This is an automated email from the git hooks/post-receive script.
praiskup pushed a commit to branch master
in repository copr/copr.
commit 23e39cce2d80703c142ec26bd5ad7ed633d9f807
Author: Jakub Kadlcik <frostyx(a)email.cz>
AuthorDate: Tue Jun 18 00:01:05 2019 +0200
[frontend] implement pinned projects feature
See #495
---
.../55a07cb7bd68_add_table_for_pinnedcoprs.py | 36 +++++++++++
frontend/coprs_frontend/coprs/config.py | 3 +
frontend/coprs_frontend/coprs/forms.py | 22 +++++++
frontend/coprs_frontend/coprs/helpers.py | 12 ++++
.../coprs_frontend/coprs/logic/complex_logic.py | 10 +++
frontend/coprs_frontend/coprs/logic/coprs_logic.py | 47 +++++++++++++-
frontend/coprs_frontend/coprs/models.py | 16 +++++
.../coprs_frontend/coprs/templates/_helpers.html | 10 ++-
.../coprs/templates/coprs/group_show.html | 4 +-
.../coprs_frontend/coprs/templates/coprs/show.html | 4 +-
.../coprs/templates/coprs/show/group.html | 14 ++--
.../coprs/templates/coprs/show/user.html | 13 +++-
.../coprs_frontend/coprs/templates/pinned.html | 74 ++++++++++++++++++++++
.../coprs/views/coprs_ns/coprs_general.py | 8 ++-
.../coprs/views/groups_ns/groups_general.py | 9 ++-
.../coprs/views/user_ns/user_general.py | 60 ++++++++++++++++++
.../tests/test_logic/test_coprs_logic.py | 36 ++++++++++-
17 files changed, 358 insertions(+), 20 deletions(-)
diff --git
a/frontend/coprs_frontend/alembic/schema/versions/55a07cb7bd68_add_table_for_pinnedcoprs.py
b/frontend/coprs_frontend/alembic/schema/versions/55a07cb7bd68_add_table_for_pinnedcoprs.py
new file mode 100644
index 0000000..49fdbca
--- /dev/null
+++
b/frontend/coprs_frontend/alembic/schema/versions/55a07cb7bd68_add_table_for_pinnedcoprs.py
@@ -0,0 +1,36 @@
+"""
+Add table for PinnedCoprs
+
+Revision ID: 55a07cb7bd68
+Revises: 2d8b4722918b
+Create Date: 2019-06-24 22:18:20.411614
+"""
+
+import sqlalchemy as sa
+from alembic import op
+
+
+revision = '55a07cb7bd68'
+down_revision = '1f4e04bb3618'
+
+
+def upgrade():
+ op.create_table('pinned_coprs',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.PrimaryKeyConstraint('id'),
+
+ sa.Column('copr_id', sa.Integer(), nullable=True),
+ sa.Column('user_id', sa.Integer(), nullable=True),
+ sa.Column('group_id', sa.Integer(), nullable=True),
+ sa.Column('position', sa.Integer(), nullable=False),
+
+ sa.ForeignKeyConstraint(['copr_id'], ['copr.id'], ),
+ sa.ForeignKeyConstraint(['group_id'], ['group.id'], ),
+ sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
+ )
+ op.create_index(op.f('ix_pinned_coprs_user_id'), 'pinned_coprs',
['user_id'], unique=False),
+ op.create_index(op.f('ix_pinned_coprs_group_id'), 'pinned_coprs',
['group_id'], unique=False),
+
+
+def downgrade():
+ op.drop_table('pinned_coprs')
diff --git a/frontend/coprs_frontend/coprs/config.py
b/frontend/coprs_frontend/coprs/config.py
index 1fc6ec4..8f77484 100644
--- a/frontend/coprs_frontend/coprs/config.py
+++ b/frontend/coprs_frontend/coprs/config.py
@@ -87,6 +87,9 @@ class Config(object):
# set this to {"epel-8": "rhelbeta-8"}
CHROOT_NAME_RELEASE_ALIAS = {}
+ # How many pinned projects a user or group can have
+ PINNED_PROJECTS_LIMIT = 4
+
class ProductionConfig(Config):
DEBUG = False
diff --git a/frontend/coprs_frontend/coprs/forms.py
b/frontend/coprs_frontend/coprs/forms.py
index 7811f37..cc31caf 100644
--- a/frontend/coprs_frontend/coprs/forms.py
+++ b/frontend/coprs_frontend/coprs/forms.py
@@ -14,6 +14,7 @@ except ImportError:
from flask_wtf import Form as FlaskForm
from coprs import constants
+from coprs import app
from coprs import helpers
from coprs import models
from coprs.logic.coprs_logic import CoprsLogic, MockChrootsLogic
@@ -1098,6 +1099,27 @@ class ModifyChrootForm(ChrootForm):
upload_comps = FileField("Upload comps.xml")
delete_comps = wtforms.BooleanField("Delete comps.xml",
false_values=FALSE_VALUES)
+
+class PinnedCoprsForm(FlaskForm):
+ copr_ids = wtforms.SelectMultipleField(wtforms.IntegerField("Pinned Copr
ID"))
+
+ def validate(self):
+ if any([i and not i.isnumeric() for i in self.copr_ids.data]):
+ self.errors["coprs"] = ["Unexpected value selected"]
+ return False
+
+ limit = app.config["PINNED_PROJECTS_LIMIT"]
+ if len(self.copr_ids.data) > limit:
+ self.errors["coprs"] = ["Too many pinned projects. Limit is
{}!".format(limit)]
+ return False
+
+ if len(list(filter(None, self.copr_ids.data))) != len(set(filter(None,
self.copr_ids.data))):
+ self.errors["coprs"] = ["You can pin a particular project only
once"]
+ return False
+
+ return True
+
+
class AdminPlaygroundForm(FlaskForm):
playground = wtforms.BooleanField("Playground", false_values=FALSE_VALUES)
diff --git a/frontend/coprs_frontend/coprs/helpers.py
b/frontend/coprs_frontend/coprs/helpers.py
index cf15033..49bd9f9 100644
--- a/frontend/coprs_frontend/coprs/helpers.py
+++ b/frontend/coprs_frontend/coprs/helpers.py
@@ -349,6 +349,18 @@ def copr_url(view, copr, **kwargs):
return url_for(view, username=copr.user.name, coprname=copr.name, **kwargs)
+def owner_url(owner):
+ """
+ For a given `owner` object, which may be either `models.User` or `models.Group`,
+ return an URL to its _profile_ page.
+ """
+ # We can't check whether owner is instance of `models.Group` because once
+ # we include models from helpers, we get circular imports
+ if hasattr(owner, "at_name"):
+ return url_for("groups_ns.list_projects_by_group",
group_name=owner.name)
+ return url_for("coprs_ns.coprs_by_user", username=owner.username)
+
+
def url_for_copr_view(view, group_view, copr, **kwargs):
if copr.is_a_group_project:
return url_for(group_view, group_name=copr.group.name, coprname=copr.name,
**kwargs)
diff --git a/frontend/coprs_frontend/coprs/logic/complex_logic.py
b/frontend/coprs_frontend/coprs/logic/complex_logic.py
index 9401261..af2a407 100644
--- a/frontend/coprs_frontend/coprs/logic/complex_logic.py
+++ b/frontend/coprs_frontend/coprs/logic/complex_logic.py
@@ -212,6 +212,16 @@ class ComplexLogic(object):
running=running,
)
+ @classmethod
+ def get_coprs_permissible_by_user(cls, user):
+ coprs = CoprsLogic.filter_without_group_projects(
+ CoprsLogic.get_multiple_owned_by_username(
+ flask.g.user.username, include_unlisted_on_hp=False)).all()
+
+ for group in user.user_groups:
+ coprs.extend(CoprsLogic.get_multiple_by_group_id(group.id).all())
+
+ return coprs
class ProjectForking(object):
diff --git a/frontend/coprs_frontend/coprs/logic/coprs_logic.py
b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
index 2c5dbab..0372774 100644
--- a/frontend/coprs_frontend/coprs/logic/coprs_logic.py
+++ b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
@@ -136,8 +136,8 @@ class CoprsLogic(object):
# user_relation="owned", username=username, with_mock_chroots=False
@classmethod
- def get_multiple_owned_by_username(cls, username):
- query = cls.get_multiple()
+ def get_multiple_owned_by_username(cls, username, include_unlisted_on_hp=True):
+ query = cls.get_multiple(include_unlisted_on_hp=include_unlisted_on_hp)
return query.filter(models.User.username == username)
@classmethod
@@ -159,6 +159,10 @@ class CoprsLogic(object):
return query.filter(models.Copr.group_id.is_(None))
@classmethod
+ def filter_without_ids(cls, query, ids):
+ return query.filter(models.Copr.id.notin_(ids))
+
+ @classmethod
def join_builds(cls, query):
return (query.outerjoin(models.Copr.builds)
.options(db.contains_eager(models.Copr.builds))
@@ -874,3 +878,42 @@ class MockChrootsLogic(object):
chroots[chroot.name] = chroot.is_active
return chroots
+
+
+class PinnedCoprsLogic(object):
+
+ @classmethod
+ def get_all(cls):
+ return
db.session.query(models.PinnedCoprs).order_by(models.PinnedCoprs.position)
+
+ @classmethod
+ def get_by_id(cls, pin_id):
+ return cls.get_all().filter(models.PinnedCoprs.id == pin_id)
+
+ @classmethod
+ def get_by_owner(cls, owner):
+ if isinstance(owner, models.Group):
+ return cls.get_by_group_id(owner.id)
+ return cls.get_by_user_id(owner.id)
+
+ @classmethod
+ def get_by_user_id(cls, user_id):
+ return cls.get_all().filter(models.PinnedCoprs.user_id == user_id)
+
+ @classmethod
+ def get_by_group_id(cls, group_id):
+ return cls.get_all().filter(models.PinnedCoprs.group_id == group_id)
+
+ @classmethod
+ def add(cls, owner, copr_id, position):
+ kwargs = dict(copr_id=copr_id, position=position)
+ kwargs["group_id" if isinstance(owner, models.Group) else
"user_id"] = owner.id
+ pin = models.PinnedCoprs(**kwargs)
+ db.session.add(pin)
+
+ @classmethod
+ def delete_by_owner(cls, owner):
+ query = db.session.query(models.PinnedCoprs)
+ if isinstance(owner, models.Group):
+ return query.filter(models.PinnedCoprs.group_id == owner.id).delete()
+ return query.filter(models.PinnedCoprs.user_id == owner.id).delete()
diff --git a/frontend/coprs_frontend/coprs/models.py
b/frontend/coprs_frontend/coprs/models.py
index 7567137..28a0d41 100644
--- a/frontend/coprs_frontend/coprs/models.py
+++ b/frontend/coprs_frontend/coprs/models.py
@@ -195,6 +195,22 @@ class User(db.Model, helpers.Serializer):
return ""
+class PinnedCoprs(db.Model, helpers.Serializer):
+ """
+ Representation of User or Group <-> Copr relation
+ """
+ id = db.Column(db.Integer, primary_key=True)
+
+ copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"))
+ user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True,
index=True)
+ group_id = db.Column(db.Integer, db.ForeignKey("group.id"), nullable=True,
index=True)
+ position = db.Column(db.Integer, nullable=False)
+
+ copr = db.relationship("Copr")
+ user = db.relationship("User")
+ group = db.relationship("Group")
+
+
class _CoprPublic(db.Model, helpers.Serializer, CoprSearchRelatedData):
"""
Represents public part of a single copr (personal repo with builds, mock
diff --git a/frontend/coprs_frontend/coprs/templates/_helpers.html
b/frontend/coprs_frontend/coprs/templates/_helpers.html
index ed65088..bdeae03 100644
--- a/frontend/coprs_frontend/coprs/templates/_helpers.html
+++ b/frontend/coprs_frontend/coprs/templates/_helpers.html
@@ -686,7 +686,8 @@
https://admin.fedoraproject.org/accounts/group/view/{{name}}
{% endmacro %}
-{% macro render_project_box(copr) %}
+{% macro render_project_box(copr, pinned=False) %}
+<!--copr-project-->
<a href="{{ copr_details_href(copr) }}"
class="list-group-item">
<div>
<h3 class="list-group-item-heading" style="display:
inline;">
@@ -696,6 +697,13 @@
https://admin.fedoraproject.org/accounts/group/view/{{name}}
<small> (temporary project, {{ copr.delete_after_msg }})</small>
{% endif %}
</div>
+
+ {% if pinned %}
+ <span class="pull-right" title="Pinned project">
+ <i class="fa fa-thumb-tack fa-lg"
aria-hidden="true"></i>
+ </span>
+ {% endif %}
+
<span class="list-group-item-text">
{{ copr.description|markdown|remove_anchor|default('Description not filled in
by author. Very likely personal repository for testing purpose, which you should not
use.', true) }}
<ul class="list-inline text-muted">
diff --git a/frontend/coprs_frontend/coprs/templates/coprs/group_show.html
b/frontend/coprs_frontend/coprs/templates/coprs/group_show.html
index a715717..bbcf3f6 100644
--- a/frontend/coprs_frontend/coprs/templates/coprs/group_show.html
+++ b/frontend/coprs_frontend/coprs/templates/coprs/group_show.html
@@ -14,8 +14,8 @@
{% endblock %}
<div class="panel panel-default">
<div class="list-group">
- {% for copr in coprs %}
- {{ render_project_box(copr) }}
+ {% for copr in pinned + coprs %}
+ {{ render_project_box(copr, pinned = copr in pinned) }}
{% else %}
<p>No projects...</p>
{% endfor %}
diff --git a/frontend/coprs_frontend/coprs/templates/coprs/show.html
b/frontend/coprs_frontend/coprs/templates/coprs/show.html
index e4f4df5..b8586cb 100644
--- a/frontend/coprs_frontend/coprs/templates/coprs/show.html
+++ b/frontend/coprs_frontend/coprs/templates/coprs/show.html
@@ -17,8 +17,8 @@
<div class="panel panel-default">
<div class="list-group">
- {% for copr in coprs %}
- {{ render_project_box(copr) }}
+ {% for copr in pinned + coprs %}
+ {{ render_project_box(copr, pinned=copr in pinned) }}
{% else %}
<p>No projects...</p>
{% endfor %}
diff --git a/frontend/coprs_frontend/coprs/templates/coprs/show/group.html
b/frontend/coprs_frontend/coprs/templates/coprs/show/group.html
index fe1619f..12555a2 100644
--- a/frontend/coprs_frontend/coprs/templates/coprs/show/group.html
+++ b/frontend/coprs_frontend/coprs/templates/coprs/show/group.html
@@ -27,10 +27,16 @@
{% block projects_header %}
{% if g.user and g.user.can_build_in_group(group) %}
-<a href="{{url_for('coprs_ns.copr_add', group_name=group.name) }}"
class="btn button-new pull-right btn-primary">
- <span class="pficon pficon-add-circle-o"></span>
- New Group Project
-</a>
+<div class="btn-group pull-right" role="group"
aria-label="Basic example">
+ <a href="{{url_for('coprs_ns.copr_add', group_name=group.name) }}"
class="btn button-new btn-primary">
+ <span class="pficon pficon-add-circle-o"></span>
+ New Group Project
+ </a>
+
+ <a href="{{ url_for('user_ns.pinned_projects', group_name=group.name)
}}" class="btn button-new btn-secondary">
+ <span class="pficon pficon-edit"></span> Customize pinned
+ </a>
+</div>
{% endif %}
<h2 class="page-title">Projects in @{{group.name}} Group</h2>
diff --git a/frontend/coprs_frontend/coprs/templates/coprs/show/user.html
b/frontend/coprs_frontend/coprs/templates/coprs/show/user.html
index 322cf0f..3d906bf 100644
--- a/frontend/coprs_frontend/coprs/templates/coprs/show/user.html
+++ b/frontend/coprs_frontend/coprs/templates/coprs/show/user.html
@@ -43,9 +43,16 @@
{% block projects_header %}
{% if g.user == user %}
-<a href="{{url_for('coprs_ns.copr_add', username=g.user.name) }}"
class="btn button-new pull-right btn-primary">
- <span class="pficon pficon-add-circle-o"></span> New Project
-</a>
+<div class="btn-group pull-right" role="group"
aria-label="Basic example">
+
+ <a href="{{url_for('coprs_ns.copr_add', username=g.user.name) }}"
class="btn button-new btn-primary">
+ <span class="pficon pficon-add-circle-o"></span> New Project
+ </a>
+
+ <a href="{{ url_for('user_ns.pinned_projects') }}" class="btn
button-new btn-secondary">
+ <span class="pficon pficon-edit"></span> Customize pinned
+ </a>
+</div>
{% endif %}
<h2 class="page-title">{{user.name|capitalize}}'s
Projects</h2>
diff --git a/frontend/coprs_frontend/coprs/templates/pinned.html
b/frontend/coprs_frontend/coprs/templates/pinned.html
new file mode 100644
index 0000000..5481ef2
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/templates/pinned.html
@@ -0,0 +1,74 @@
+{% extends "coprs/show.html" %}
+{% from "_helpers.html" import render_form_errors %}
+{% block title %}Customize pinned projects{% endblock %}
+{% block header %}Customize pinned projects{% endblock %}
+{% block breadcrumbs %}
+<ol class="breadcrumb">
+ <li>
+ <a href="{{ url_for('coprs_ns.coprs_show')
}}">Home</a>
+ </li>
+ <li>
+ {% if owner.at_name is defined %}
+ <a href="{{ url_for('groups_ns.list_projects_by_group',
group_name=owner.name) }}">{{ owner.at_name }}</a>
+ {% else %}
+ <a href="{{ url_for('coprs_ns.coprs_by_user',
username=owner.name) }}">{{ owner.name }}</a>
+ {% endif %}
+ </li>
+ <li class="active">
+ Customize pinned projects
+ </li>
+</ol>
+{% endblock %}
+
+{% block content %}
+<h1 style="margin-bottom:22px;margin-top:22px">Pinned
projects</h1>
+
+{% if form %}
+ {{ render_form_errors(form=form) }}
+{% endif %}
+
+<form action="" method="POST">
+ <div class="panel panel-default">
+
+ <div class="panel-heading">
+ Customize pinned projects for
+ {{ owner.at_name if owner.at_name is defined else owner.name }}
+ </div>
+
+ <div class="panel-body">
+ <p>
+ Configure up to four pinned projects, that you are particularly proud of or
recognized for. They will be displayed
+ on the top of your user/group page the exact order. It is possible to select your
personal projects, group
+ projects and even someone else's projects, that you have permissions for.
+ </p>
+ </div>
+
+ <table class="table table-bordered">
+ <thead>
+ <tr>
+ <th>Slot</th>
+ <th>Project</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ {% for i in range(0, config.PINNED_PROJECTS_LIMIT) %}
+ <tr>
+ <td>#{{ i + 1 }}</td>
+ <td>
+ <select name="copr_ids" class="input">
+ <option value="">Nothing</option>
+ {% for copr in coprs %}
+ {% set selected = 'selected' if copr.id == selected[i] else
'' %}
+ <option value="{{ copr.id }}" {{ selected }}>{{
copr.full_name }}</option>
+ {% endfor %}
+ </select>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ <input class="btn btn-primary pull-right" type="submit"
name="submit" value="Submit">
+ </div>
+</form>
+{% endblock %}
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 ea18ac7..9bb39cd 100644
--- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
+++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
@@ -28,7 +28,7 @@ from coprs import forms
from coprs import helpers
from coprs import models
from coprs.exceptions import ObjectNotFound
-from coprs.logic.coprs_logic import CoprsLogic
+from coprs.logic.coprs_logic import CoprsLogic, PinnedCoprsLogic
from coprs.logic.stat_logic import CounterStatLogic
from coprs.logic.modules_logic import ModulesLogic, ModulemdGenerator, ModuleBuildFacade
from coprs.rmodels import TimedStatEvents
@@ -77,6 +77,7 @@ def coprs_show(page=1):
return flask.render_template("coprs/show/all.html",
coprs=coprs,
+ pinned=[],
paginator=paginator,
tasks_info=ComplexLogic.get_queue_sizes(),
users_builds=users_builds,
@@ -91,12 +92,13 @@ def coprs_by_user(username=None, page=1):
return page_not_found(
"User {0} does not exist.".format(username))
+ pinned = [pin.copr for pin in PinnedCoprsLogic.get_by_user_id(user.id)] if page == 1
else []
query = CoprsLogic.get_multiple_owned_by_username(username)
+ query = CoprsLogic.filter_without_ids(query, [copr.id for copr in pinned])
query = CoprsLogic.filter_without_group_projects(query)
query = CoprsLogic.set_query_order(query, desc=True)
paginator = helpers.Paginator(query, query.count(), page)
-
coprs = paginator.sliced_query
# flask.g.user is none when no user is logged - showing builds from everyone
@@ -107,6 +109,7 @@ def coprs_by_user(username=None, page=1):
return flask.render_template("coprs/show/user.html",
user=user,
coprs=coprs,
+ pinned=pinned,
paginator=paginator,
tasks_info=ComplexLogic.get_queue_sizes(),
users_builds=users_builds,
@@ -132,6 +135,7 @@ def coprs_fulltext_search(page=1):
coprs = paginator.sliced_query
return render_template("coprs/show/fulltext.html",
coprs=coprs,
+ pinned=[],
paginator=paginator,
fulltext=fulltext,
tasks_info=ComplexLogic.get_queue_sizes(),
diff --git a/frontend/coprs_frontend/coprs/views/groups_ns/groups_general.py
b/frontend/coprs_frontend/coprs/views/groups_ns/groups_general.py
index 848a745..bc6b0e6 100644
--- a/frontend/coprs_frontend/coprs/views/groups_ns/groups_general.py
+++ b/frontend/coprs_frontend/coprs/views/groups_ns/groups_general.py
@@ -7,12 +7,13 @@ from coprs.forms import ActivateFasGroupForm
from coprs.helpers import Paginator
from coprs.logic import builds_logic
from coprs.logic.complex_logic import ComplexLogic
-from coprs.logic.coprs_logic import CoprsLogic
+from coprs.logic.coprs_logic import CoprsLogic, PinnedCoprsLogic
from coprs.logic.users_logic import UsersLogic
from coprs import app
from ... import db
from ..misc import login_required
+from ..user_ns import user_general
from . import groups_ns
@@ -60,10 +61,11 @@ def activate_group(fas_group):
@groups_ns.route("/g/<group_name>/coprs/<int:page>")
def list_projects_by_group(group_name, page=1):
group = ComplexLogic.get_group_by_name_safe(group_name)
- query = CoprsLogic.get_multiple_by_group_id(group.id)
+ pinned = [pin.copr for pin in PinnedCoprsLogic.get_by_group_id(group.id)] if page ==
1 else []
+ query = CoprsLogic.get_multiple_by_group_id(group.id)
+ query = CoprsLogic.filter_without_ids(query, [copr.id for copr in pinned])
paginator = Paginator(query, query.count(), page)
-
coprs = paginator.sliced_query
data = builds_logic.BuildsLogic.get_small_graph_data('30min')
@@ -72,6 +74,7 @@ def list_projects_by_group(group_name, page=1):
"coprs/show/group.html",
user=flask.g.user,
coprs=coprs,
+ pinned=pinned,
paginator=paginator,
tasks_info=ComplexLogic.get_queue_sizes(),
group=group,
diff --git a/frontend/coprs_frontend/coprs/views/user_ns/user_general.py
b/frontend/coprs_frontend/coprs/views/user_ns/user_general.py
index 76e83a6..575dbcf 100644
--- a/frontend/coprs_frontend/coprs/views/user_ns/user_general.py
+++ b/frontend/coprs_frontend/coprs/views/user_ns/user_general.py
@@ -1,9 +1,12 @@
import flask
from . import user_ns
+from coprs import app, db, models, helpers
+from coprs.forms import PinnedCoprsForm
from coprs.views.misc import login_required
from coprs.logic.users_logic import UsersLogic, UserDataDumper
from coprs.logic.builds_logic import BuildsLogic
from coprs.logic.complex_logic import ComplexLogic
+from coprs.logic.coprs_logic import CoprsLogic, PinnedCoprsLogic
def render_user_info(user):
@@ -37,3 +40,60 @@ def delete_data():
UsersLogic.delete_user_data(flask.g.user.username)
flask.flash("Your data were successfully deleted.")
return render_user_info(flask.g.user)
+
+
+(a)user_ns.route("/customize-pinned/")
+(a)user_ns.route("/customize-pinned/<group_name>")
+@login_required
+def pinned_projects(group_name=None):
+ owner = flask.g.user if not group_name else
ComplexLogic.get_group_by_name_safe(group_name)
+ return render_pinned_projects(owner)
+
+
+def render_pinned_projects(owner, form=None):
+ pinned = [pin.copr for pin in PinnedCoprsLogic.get_by_owner(owner)]
+ if isinstance(owner, models.Group):
+ UsersLogic.raise_if_not_in_group(flask.g.user, owner)
+ coprs =
CoprsLogic.get_multiple_by_group_id(owner.id).filter(models.Copr.unlisted_on_hp.is_(False)).all()
+ else:
+ coprs = ComplexLogic.get_coprs_permissible_by_user(owner)
+ coprs = sorted(coprs, key=lambda copr: copr.full_name)
+ selected = [copr.id for copr in pinned]
+ selected += (app.config["PINNED_PROJECTS_LIMIT"] - len(pinned)) * [None]
+ for i, copr_id in enumerate(form.copr_ids.data if form else []):
+ selected[i] = int(copr_id) if copr_id else None
+
+ graph = BuildsLogic.get_small_graph_data('30min')
+ return flask.render_template("pinned.html",
+ owner=owner,
+ pinned=pinned,
+ selected=selected,
+ coprs=coprs,
+ form=form,
+ tasks_info=ComplexLogic.get_queue_sizes(),
+ graph=graph)
+
+
+(a)user_ns.route("/customize-pinned/", methods=["POST"])
+(a)user_ns.route("/customize-pinned/<group_name>",
methods=["POST"])
+@login_required
+def pinned_projects_post(group_name=None):
+ owner = flask.g.user if not group_name else
ComplexLogic.get_group_by_name_safe(group_name)
+ url_on_success = helpers.owner_url(owner)
+ return process_pinned_projects_post(owner, url_on_success)
+
+
+def process_pinned_projects_post(owner, url_on_success):
+ if isinstance(owner, models.Group):
+ UsersLogic.raise_if_not_in_group(flask.g.user, owner)
+
+ form = PinnedCoprsForm()
+ if not form.validate_on_submit():
+ return render_pinned_projects(owner, form=form)
+
+ PinnedCoprsLogic.delete_by_owner(owner)
+ for i, copr_id in enumerate(filter(None, form.copr_ids.data)):
+ PinnedCoprsLogic.add(owner, int(copr_id), i)
+ db.session.commit()
+
+ return flask.redirect(url_on_success)
diff --git a/frontend/coprs_frontend/tests/test_logic/test_coprs_logic.py
b/frontend/coprs_frontend/tests/test_logic/test_coprs_logic.py
index b752abf..e869d4d 100644
--- a/frontend/coprs_frontend/tests/test_logic/test_coprs_logic.py
+++ b/frontend/coprs_frontend/tests/test_logic/test_coprs_logic.py
@@ -8,8 +8,9 @@ from sqlalchemy import desc
from copr_common.enums import ActionTypeEnum
from coprs import app
+from coprs.forms import PinnedCoprsForm
from coprs.logic.actions_logic import ActionsLogic
-from coprs.logic.coprs_logic import CoprsLogic, CoprChrootsLogic
+from coprs.logic.coprs_logic import CoprsLogic, CoprChrootsLogic, PinnedCoprsLogic
from coprs.logic.users_logic import UsersLogic
from coprs import models
@@ -133,3 +134,36 @@ class TestCoprChrootsLogic(CoprsTestCase):
# However, it should not be removed from the Copr
assert [ch.name for ch in self.c2.copr_chroots] == ["fedora-17-x86_64",
"fedora-17-i386"]
+
+
+class TestPinnedCoprsLogic(CoprsTestCase):
+
+ def test_pinned_projects(self, f_users, f_coprs, f_db):
+ assert set(CoprsLogic.get_multiple_by_username(self.u2.name)) == {self.c2,
self.c3}
+ assert set(PinnedCoprsLogic.get_by_owner(self.u2)) == set()
+
+ pc1 = models.PinnedCoprs(id=1, copr_id=self.c2.id, user_id=self.u2.id,
position=1)
+ pc2 = models.PinnedCoprs(id=2, copr_id=self.c3.id, user_id=self.u2.id,
position=2)
+ self.db.session.add_all([pc1, pc2])
+
+ assert set(PinnedCoprsLogic.get_by_owner(self.u2)) == {pc1, pc2}
+ assert set(CoprsLogic.get_multiple_by_username(self.u2.name)) == {self.c2,
self.c3}
+
+ def test_limit(self):
+ app.config["PINNED_PROJECTS_LIMIT"] = 1
+ with app.app_context():
+ form = PinnedCoprsForm()
+ form.copr_ids.data = ["1"]
+ assert form.validate()
+
+ form.copr_ids.data = ["1", "2"]
+ assert not form.validate()
+ assert "Too many" in form.errors["coprs"][0]
+
+ def test_unique_coprs(self):
+ app.config["PINNED_PROJECTS_LIMIT"] = 2
+ with app.app_context():
+ form = PinnedCoprsForm()
+ form.copr_ids.data = ["1", "1"]
+ assert not form.validate()
+ assert "only once" in form.errors["coprs"][0]
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.