Repository :
http://git.fedorahosted.org/cgit/copr.git
On branch : master
---------------------------------------------------------------
commit f990384a5f964780edb71557007b963713e1e4aa
Author: Valentin Gologuzov <vgologuz(a)redhat.com>
Date: Tue Sep 29 17:29:19 2015 +0200
[frontend] Support group projects, Work In progress
---------------------------------------------------------------
frontend/coprs_frontend/coprs/forms.py | 14 ++++
frontend/coprs_frontend/coprs/logic/coprs_logic.py | 46 +++++++++++--
frontend/coprs_frontend/coprs/logic/users_logic.py | 33 +++++++++-
frontend/coprs_frontend/coprs/models.py | 25 ++++++-
frontend/coprs_frontend/coprs/redis_session.py | 1 +
.../templates/coprs/detail/make_group_project.html | 35 ++++++++++
.../coprs/templates/coprs/detail/overview.html | 16 +++++
.../coprs/views/coprs_ns/coprs_general.py | 68 ++++++++++++++++++--
frontend/coprs_frontend/coprs/views/misc.py | 2 +-
9 files changed, 221 insertions(+), 19 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/forms.py
b/frontend/coprs_frontend/coprs/forms.py
index 7ffe436..ea8f795 100644
--- a/frontend/coprs_frontend/coprs/forms.py
+++ b/frontend/coprs_frontend/coprs/forms.py
@@ -506,3 +506,17 @@ class AdminPlaygroundForm(wtf.Form):
class AdminPlaygroundSearchForm(wtf.Form):
project = wtforms.TextField("Project")
+
+
+def group_managed_form_fabric(available_fas_groups):
+ """
+ :type available_fas_groups: list of str
+ """
+ choices = [(x, x) for x in available_fas_groups]
+
+ class GroupManagedForm(wtf.Form):
+
+ name = wtforms.StringField()
+ fas_name = wtforms.SelectField(choices=choices)
+
+ return GroupManagedForm()
diff --git a/frontend/coprs_frontend/coprs/logic/coprs_logic.py
b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
index ec56f0a..03ed0a2 100644
--- a/frontend/coprs_frontend/coprs/logic/coprs_logic.py
+++ b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
@@ -39,6 +39,22 @@ class CoprsLogic(object):
return cls.get_all().filter(models.Copr.id == copr_id)
@classmethod
+ def _attach_build(cls, query):
+ query = (query.outerjoin(models.Copr.builds)
+ .options(db.contains_eager(models.Copr.builds))
+ .order_by(models.Build.submitted_on.desc()))
+ return query
+
+ @classmethod
+ def _attach_mock_chroots(cls, query):
+ query = (query.outerjoin(*models.Copr.mock_chroots.attr)
+ .options(db.contains_eager(*models.Copr.mock_chroots.attr))
+ .order_by(models.MockChroot.os_release.asc())
+ .order_by(models.MockChroot.os_version.asc())
+ .order_by(models.MockChroot.arch.asc()))
+ return query
+
+ @classmethod
def get(cls, username, coprname, **kwargs):
with_builds = kwargs.get("with_builds", False)
with_mock_chroots = kwargs.get("with_mock_chroots", False)
@@ -50,19 +66,33 @@ class CoprsLogic(object):
)
if with_builds:
- query = (query.outerjoin(models.Copr.builds)
- .options(db.contains_eager(models.Copr.builds))
- .order_by(models.Build.submitted_on.desc()))
+ query = cls._attach_build(query)
+
+ if with_mock_chroots:
+ query = cls._attach_mock_chroots(query)
+
+ return query
+
+ @classmethod
+ def get_by_group_id(cls, group_id, coprname, **kwargs):
+ with_builds = kwargs.get("with_builds", False)
+ with_mock_chroots = kwargs.get("with_mock_chroots", False)
+
+ query = (
+ cls.get_all()
+ .filter(models.Copr.group_id == group_id)
+ .filter(models.Copr.name == coprname)
+ )
+
+ if with_builds:
+ query = cls._attach_build(query)
if with_mock_chroots:
- query = (query.outerjoin(*models.Copr.mock_chroots.attr)
- .options(db.contains_eager(*models.Copr.mock_chroots.attr))
- .order_by(models.MockChroot.os_release.asc())
- .order_by(models.MockChroot.os_version.asc())
- .order_by(models.MockChroot.arch.asc()))
+ query = cls._attach_mock_chroots(query)
return query
+
@classmethod
def get_multiple(cls, include_deleted=False):
query = (
diff --git a/frontend/coprs_frontend/coprs/logic/users_logic.py
b/frontend/coprs_frontend/coprs/logic/users_logic.py
index 3d494e8..68092a4 100644
--- a/frontend/coprs_frontend/coprs/logic/users_logic.py
+++ b/frontend/coprs_frontend/coprs/logic/users_logic.py
@@ -1,6 +1,7 @@
from coprs import exceptions
-from coprs.models import User
+from coprs import db
+from coprs.models import User, Group
class UsersLogic(object):
@@ -34,3 +35,33 @@ class UsersLogic(object):
if not user.can_build_in(copr):
raise exceptions.InsufficientRightsException(message)
+
+ @classmethod
+ def get_group_by_alias(cls, name):
+ return Group.query.filter(Group.name == name)
+
+ @classmethod
+ def get_group_by_fas_name(cls, fas_name):
+ return Group.query.filter(Group.fas_name == fas_name)
+
+ @classmethod
+ def create_group_by_fas_name(cls, fas_name, alias=None):
+ if alias is None:
+ alias = fas_name
+
+ group = Group(
+ fas_name=fas_name,
+ name=alias,
+ )
+ db.session.add(group)
+ return group
+
+ @classmethod
+ def get_group_by_fas_name_or_create(cls, fas_name, alias=None):
+ mb_group = cls.get_group_by_fas_name(fas_name).first()
+ if mb_group is not None:
+ return mb_group
+
+ group = cls.create_group_by_fas_name(fas_name, alias)
+ db.session.flush()
+ return group
diff --git a/frontend/coprs_frontend/coprs/models.py
b/frontend/coprs_frontend/coprs/models.py
index dac42fd..2a6db9b 100644
--- a/frontend/coprs_frontend/coprs/models.py
+++ b/frontend/coprs_frontend/coprs/models.py
@@ -2,6 +2,7 @@ import copy
import datetime
import json
import os
+import flask
from sqlalchemy.ext.associationproxy import association_proxy
from libravatar import libravatar_url
@@ -87,6 +88,11 @@ class User(db.Model, helpers.Serializer):
can_build = True
+ # a bit dirty code, here we access flask.session object
+ if copr.group is not None and \
+ copr.group.fas_name in flask.session.get("teams", []):
+ return True
+
return can_build
def can_edit(self, copr):
@@ -94,16 +100,20 @@ class User(db.Model, helpers.Serializer):
Determine if this user can edit the given copr.
"""
- can_edit = False
if copr.owner == self or self.admin:
- can_edit = True
+ return True
if (self.permissions_for_copr(copr) and
self.permissions_for_copr(copr).copr_admin ==
helpers.PermissionEnum("approved")):
- can_edit = True
+ return True
- return can_edit
+ # a bit dirty code, here we access flask.session object
+ if copr.group is not None and \
+ copr.group.fas_name in flask.session.get("teams", []):
+ return True
+
+ return False
@property
def serializable_attributes(self):
@@ -173,6 +183,10 @@ class Copr(db.Model, helpers.Serializer):
}
@property
+ def is_a_group_project(self):
+ return self.group_id is not None
+
+ @property
def owner_name(self):
return self.owner.name
@@ -855,12 +869,15 @@ class CounterStat(db.Model, helpers.Serializer):
counter = db.Column(db.Integer, default=0, server_default="0")
+
class Group(db.Model, helpers.Serializer):
"""
Represents FAS groups and their aliases in Copr
"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(127))
+
+ # TODO: add unique=True
fas_name = db.Column(db.String(127))
def __str__(self):
diff --git a/frontend/coprs_frontend/coprs/redis_session.py
b/frontend/coprs_frontend/coprs/redis_session.py
index d345777..5ec89b6 100644
--- a/frontend/coprs_frontend/coprs/redis_session.py
+++ b/frontend/coprs_frontend/coprs/redis_session.py
@@ -54,6 +54,7 @@ class RedisSessionInterface(SessionInterface):
return self.session_class(sid=sid, new=True)
def save_session(self, app, session, response):
+ # print("String session: {}".format(session))
domain = self.get_cookie_domain(app)
if not session:
self.redis.delete(self.prefix + session.sid)
diff --git a/frontend/coprs_frontend/coprs/templates/coprs/detail/make_group_project.html
b/frontend/coprs_frontend/coprs/templates/coprs/detail/make_group_project.html
new file mode 100644
index 0000000..e1fb714
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/templates/coprs/detail/make_group_project.html
@@ -0,0 +1,35 @@
+{% extends "coprs/detail.html" %}
+
+{% from "_helpers.html" import render_field, render_form_errors %}
+
+{% block title %}
+ Change project {{ copr.owner.name }}/{{ copr.name }} into a group managed one/
+
+{% endblock %}
+{%block project_breadcrumb %}
+{#
+<!--<li>-->
+ <!--<a href="{{ url_for('coprs_ns.copr_builds',
username=copr.owner.name, coprname=copr.name) }}">Packages</a>-->
+<!--</li>-->
+#}
+<li class="active">
+
+</li>
+{%endblock%}
+
+{% block detail_body %}
+ <h3>
+ Select Fas project to bound project
+ </h3>
+ <form class="form-horizontal"
+ action="{{ url_for('coprs_ns.copr_group_managed',
username=copr.owner.name, coprname=copr.name) }}"
+ method="post" enctype="multipart/form-data">
+ {{ form.csrf_token }}
+
+ {{ render_field(form.name, label="Group alias to use in the Copr") }}
+ {{ render_field(form.fas_name) }}
+
+ <input class="btn btn-primary" type="submit"
value="Submit">
+ </form>
+
+{% endblock %}
diff --git a/frontend/coprs_frontend/coprs/templates/coprs/detail/overview.html
b/frontend/coprs_frontend/coprs/templates/coprs/detail/overview.html
index d5254cc..c4f69e9 100644
--- a/frontend/coprs_frontend/coprs/templates/coprs/detail/overview.html
+++ b/frontend/coprs_frontend/coprs/templates/coprs/detail/overview.html
@@ -25,6 +25,13 @@
The following unofficial repositories are provided as-is by owner of this
project.
Contact the owner directly for bugs or issues (IE: not bugzilla).
</p>
+
+ <p>
+ {% if copr.is_a_group_project %}
+ This project is managed by FAS group {{ copr.group.fas_name }}
+ {% endif %}
+ </p>
+
<table class="table table-striped table-bordered">
<thead>
<tr>
@@ -162,9 +169,18 @@
<h3 class="panel-title"> Other Actions </h3>
</div>
<div class="panel-body">
+ {% if user.can_edit(copr) and not copr.is_a_group_project %}
+ <a href="{{ url_for('coprs_ns.copr_group_managed', username =
copr.owner.name, coprname = copr.name) }}">
+ Change to a group project
+ </a>
+ {% endif %}
+
+
<a href="{{ url_for('coprs_ns.copr_report_abuse', username =
copr.owner.name, coprname = copr.name) }}">
<small class="pficon pficon-warning-triangle-o"> Report Abuse
</small></a>
+
</div>
+
</div>
</div>
</div>
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 7d90c0b..2891ca8 100644
--- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
+++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
@@ -19,8 +19,10 @@ from coprs import exceptions
from coprs import forms
from coprs import helpers
from coprs import models
+from coprs.forms import group_managed_form_fabric
from coprs.logic.coprs_logic import CoprsLogic
from coprs.logic.stat_logic import CounterStatLogic
+from coprs.logic.users_logic import UsersLogic
from coprs.rmodels import TimedStatEvents
from coprs.logic.complex_logic import ComplexLogic
@@ -215,18 +217,40 @@ def copr_report_abuse(username, coprname):
form=form)
+(a)coprs_ns.route("/g/<groupname>/<coprname>/")
+def group_copr_detail(groupname, coprname):
+
+ try:
+ group = UsersLogic.get_group_by_alias(groupname).one()
+ except sqlalchemy.orm.exc.NoResultFound:
+ return page_not_found(
+ "Group {} does not exist.".format(groupname))
+
+ try:
+ copr = CoprsLogic.get_by_group_id(group.id, coprname).one()
+ except sqlalchemy.orm.exc.NoResultFound:
+ return page_not_found(
+ "Project {0} does not exist.".format(coprname))
+
+ return render_copr_detail(copr)
+
+
@coprs_ns.route("/<username>/<coprname>/")
def copr_detail(username, coprname):
query = coprs_logic.CoprsLogic.get(username, coprname, with_mock_chroots=True)
- form = forms.CoprLegalFlagForm()
+
try:
copr = query.one()
except sqlalchemy.orm.exc.NoResultFound:
return page_not_found(
"Project {0} does not exist.".format(coprname))
- repo_dl_stat = CounterStatLogic.get_copr_repo_dl_stat(copr)
+ return render_copr_detail(copr)
+
+def render_copr_detail(copr):
+ repo_dl_stat = CounterStatLogic.get_copr_repo_dl_stat(copr)
+ form = forms.CoprLegalFlagForm()
repos_info = {}
for chroot in copr.active_chroots:
# chroot_rpms_dl_stat_key = CHROOT_REPO_MD_DL_STAT_FMT.format(
@@ -260,14 +284,12 @@ def copr_detail(username, coprname):
else:
repos_info[chroot.name_release]["arch_list"].append(chroot.arch)
repos_info[chroot.name_release]["rpm_dl_stat"][chroot.arch] =
chroot_rpms_dl_stat
-
repos_info_list = sorted(repos_info.values(), key=lambda rec:
rec["name_release"])
-
builds = builds_logic.BuildsLogic.get_multiple_by_copr(copr=copr).limit(1).all()
-
return flask.render_template(
"coprs/detail/overview.html",
copr=copr,
+ user=flask.g.user,
form=form,
repo_dl_stat=repo_dl_stat,
repos_info_list=repos_info_list,
@@ -706,3 +728,39 @@ def copr_build_monitor_detailed(username, coprname):
monitor=monitor,
oses=oses_grouped,
archs=archs)
+
+
+@coprs_ns.route("/<username>/<coprname>/group_managed",
methods=["GET", "POST"])
+def copr_group_managed(username, coprname):
+ try:
+ copr = coprs_logic.CoprsLogic.get(username, coprname,
with_mock_chroots=True).one()
+ except sqlalchemy.orm.exc.NoResultFound:
+ return page_not_found(
+ "Project {0} does not exist.".format(coprname))
+
+ form = group_managed_form_fabric(flask.session.get("teams"))
+
+ if form.validate_on_submit():
+ group = UsersLogic.get_group_by_fas_name_or_create(
+ form.fas_name.data, form.name.data)
+
+ copr.group_id = group.id
+ db.session.add(copr)
+ db.session.commit()
+
+ flask.flash(
+ "Project is now managed by {} FAS group, "
+ "main url to the project: {}"
+ .format(
+ form.fas_name.data,
+ "group url todo:"
+ )
+ )
+ return flask.redirect(flask.url_for(
+ "coprs_ns.copr_detail", username=username, coprname=coprname))
+
+ else:
+ return flask.render_template(
+ "coprs/detail/make_group_project.html",
+ copr=copr, form=form,
+ )
diff --git a/frontend/coprs_frontend/coprs/views/misc.py
b/frontend/coprs_frontend/coprs/views/misc.py
index f0eb45c..440f051 100644
--- a/frontend/coprs_frontend/coprs/views/misc.py
+++ b/frontend/coprs_frontend/coprs/views/misc.py
@@ -149,7 +149,7 @@ def login():
return flask.redirect(oid.get_next_url())
else:
# todo: ask puiterwijk to enable "magic" group
- team_req = TeamsRequest(["fi-apprentice",
"_FAS_ALL_GROUPS_"])
+ team_req = TeamsRequest(["fi-apprentice", "packager",
"_FAS_ALL_GROUPS_"])
return
oid.try_login("https://id.fedoraproject.org/",
ask_for=["email", "timezone"],
extensions=[team_req])