This is an automated email from the git hooks/post-receive script.
praiskup pushed a change to branch master in repository copr/copr.
from 72f55da [doc] kill all old builders when upgrading infrastructure new 8b3b919 [frontend] separate projects_header block from show_top block new 8fe754d [frontend] fix closing tag new 47b9c41 [frontend] add missing border, same as user page has new d5812ea [frontend] extract code for rendering project box into its own macro new 23e39cc [frontend] implement pinned projects feature
The 5 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "add" were already present in the repository and have only been added to this reference.
Summary of changes: .../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 | 37 +++++++++++ .../coprs/templates/coprs/group_show.html | 39 ++++-------- .../coprs_frontend/coprs/templates/coprs/show.html | 34 ++-------- .../coprs/templates/coprs/show/all.html | 6 +- .../coprs/templates/coprs/show/fulltext.html | 2 + .../coprs/templates/coprs/show/group.html | 22 +++++-- .../coprs/templates/coprs/show/user.html | 19 ++++-- .../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 ++++++++++- 19 files changed, 419 insertions(+), 73 deletions(-) create mode 100644 frontend/coprs_frontend/alembic/schema/versions/55a07cb7bd68_add_table_for_pinnedcoprs.py create mode 100644 frontend/coprs_frontend/coprs/templates/pinned.html
This is an automated email from the git hooks/post-receive script.
praiskup pushed a commit to branch master in repository copr/copr.
commit 8b3b919d4f10c6daca9c805711317ea4fe9d4ebf Author: Jakub Kadlcik frostyx@email.cz AuthorDate: Mon Jun 17 20:25:46 2019 +0200
[frontend] separate projects_header block from show_top block --- frontend/coprs_frontend/coprs/templates/coprs/group_show.html | 3 +++ frontend/coprs_frontend/coprs/templates/coprs/show.html | 4 ++++ frontend/coprs_frontend/coprs/templates/coprs/show/all.html | 6 +++++- frontend/coprs_frontend/coprs/templates/coprs/show/fulltext.html | 2 ++ frontend/coprs_frontend/coprs/templates/coprs/show/group.html | 6 +++++- frontend/coprs_frontend/coprs/templates/coprs/show/user.html | 6 +++++- 6 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/templates/coprs/group_show.html b/frontend/coprs_frontend/coprs/templates/coprs/group_show.html index 2528f64..6a32eb7 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/group_show.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/group_show.html @@ -9,6 +9,9 @@ <div class="col-md-9 col-sm-8"> {% block show_top %} {% endblock %} + + {% block projects_header %} + {% endblock %} <div class="list-group"> {% for copr in coprs %} <!--copr-project--> diff --git a/frontend/coprs_frontend/coprs/templates/coprs/show.html b/frontend/coprs_frontend/coprs/templates/coprs/show.html index 10ed4e8..f71361f 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/show.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/show.html @@ -10,6 +10,10 @@ {% block content %} {% block show_top %} {% endblock %} + + {% block projects_header %} + {% endblock %} + <div class="panel panel-default"> <div class="list-group"> {% for copr in coprs %} diff --git a/frontend/coprs_frontend/coprs/templates/coprs/show/all.html b/frontend/coprs_frontend/coprs/templates/coprs/show/all.html index be8624a..58e7605 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/show/all.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/show/all.html @@ -1,8 +1,9 @@ {% extends "coprs/show.html" %} {% block title %}Project List{% endblock %} {% block header %}Project List{% endblock %} -{% block show_top %}
+ +{% block show_top %} {% if not g.user and not fulltext%} <br> <div class="panel panel-default"> @@ -11,7 +12,10 @@ </div> </div> {% endif %} +{% endblock %} +
+{% block projects_header %} {% if g.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 diff --git a/frontend/coprs_frontend/coprs/templates/coprs/show/fulltext.html b/frontend/coprs_frontend/coprs/templates/coprs/show/fulltext.html index fb713ec..8f309c0 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/show/fulltext.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/show/fulltext.html @@ -14,5 +14,7 @@ </ol> {% endblock %} {% block show_top %} + + <h1> Search Results <small> {{fulltext}} </small></h1> {% endblock %} diff --git a/frontend/coprs_frontend/coprs/templates/coprs/show/group.html b/frontend/coprs_frontend/coprs/templates/coprs/show/group.html index e33a6b7..eb282a9 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/show/group.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/show/group.html @@ -12,8 +12,9 @@ </li> </ol> {% endblock %} -{% block show_top %}
+ +{% block show_top %} <div id="profile"> <h1>@{{group.name}} Group</h1> <p> @@ -21,7 +22,10 @@ <a href="https://admin.fedoraproject.org/accounts/group/members/{{ group.fas_name }}" title="{{ group.fas_name }}'s Members" target="_blank">View Members</a> </h> </div> +{% endblock %} +
+{% 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> diff --git a/frontend/coprs_frontend/coprs/templates/coprs/show/user.html b/frontend/coprs_frontend/coprs/templates/coprs/show/user.html index a25eba4..322cf0f 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/show/user.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/show/user.html @@ -12,8 +12,9 @@ </li> </ol> {% endblock %} -{% block show_top %}
+ +{% block show_top %} <div id="profile"> <img src="{{ user.gravatar_url }}" alt="User Image" class="avatar"> <h1>{{user.name|capitalize}}'s Profile</h1> @@ -37,7 +38,10 @@ {% include "user_meta.html" %} </p> </div> +{% endblock %} +
+{% 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
This is an automated email from the git hooks/post-receive script.
praiskup pushed a commit to branch master in repository copr/copr.
commit 8fe754dbd45d8a3a577607694fdbf4a6d07672df Author: Jakub Kadlcik frostyx@email.cz AuthorDate: Mon Jun 17 20:27:09 2019 +0200
[frontend] fix closing tag --- frontend/coprs_frontend/coprs/templates/coprs/show/group.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/coprs_frontend/coprs/templates/coprs/show/group.html b/frontend/coprs_frontend/coprs/templates/coprs/show/group.html index eb282a9..fe1619f 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/show/group.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/show/group.html @@ -20,7 +20,7 @@ <p> <a href="https://admin.fedoraproject.org/accounts/group/view/{{ group.fas_name }}" title="{{ group.fas_name }}'s FAS details" target="_blank">FAS details</a> | <a href="https://admin.fedoraproject.org/accounts/group/members/{{ group.fas_name }}" title="{{ group.fas_name }}'s Members" target="_blank">View Members</a> - </h> + </p> </div> {% endblock %}
This is an automated email from the git hooks/post-receive script.
praiskup pushed a commit to branch master in repository copr/copr.
commit 47b9c41c3a0c2964056507abe8086747bb976d72 Author: Jakub Kadlcik frostyx@email.cz AuthorDate: Mon Jun 17 20:30:28 2019 +0200
[frontend] add missing border, same as user page has --- .../coprs/templates/coprs/group_show.html | 54 +++++++++++----------- 1 file changed, 28 insertions(+), 26 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/templates/coprs/group_show.html b/frontend/coprs_frontend/coprs/templates/coprs/group_show.html index 6a32eb7..f3bafef 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/group_show.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/group_show.html @@ -12,32 +12,34 @@
{% block projects_header %} {% endblock %} - <div class="list-group"> - {% for copr in coprs %} - <!--copr-project--> - <a href="{{ copr_details_href(copr) }}" class="list-group-item"> - <h3 class="list-group-item-heading"> - {{ copr_name(copr) }} - </h3> - <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"> - {% for os in copr.active_chroots_grouped %} - <li> - <strong>{{ friendly_os_name(os[0].split()[0], os[0].split()[1]) }}:</strong> - <small> - {% for arch in os[1] %} - {{arch}}{% if not loop.last %}, {% endif %} - {%endfor%} - </small> - </li> - {% endfor %} - </ul> - </span> - </a> - {% else %} - <p>No projects...</p> - {% endfor %} + <div class="panel panel-default"> + <div class="list-group"> + {% for copr in coprs %} + <!--copr-project--> + <a href="{{ copr_details_href(copr) }}" class="list-group-item"> + <h3 class="list-group-item-heading"> + {{ copr_name(copr) }} + </h3> + <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"> + {% for os in copr.active_chroots_grouped %} + <li> + <strong>{{ friendly_os_name(os[0].split()[0], os[0].split()[1]) }}:</strong> + <small> + {% for arch in os[1] %} + {{arch}}{% if not loop.last %}, {% endif %} + {%endfor%} + </small> + </li> + {% endfor %} + </ul> + </span> + </a> + {% else %} + <p>No projects...</p> + {% endfor %} + </div> </div> {{ render_pagination(request, paginator) }} </div>
This is an automated email from the git hooks/post-receive script.
praiskup pushed a commit to branch master in repository copr/copr.
commit d5812ea95703c0886d5c4b525ab41fd9a1d3721c Author: Jakub Kadlcik frostyx@email.cz AuthorDate: Mon Jun 17 20:51:17 2019 +0200
[frontend] extract code for rendering project box into its own macro --- .../coprs_frontend/coprs/templates/_helpers.html | 29 ++++++++++++++++++++++ .../coprs/templates/coprs/group_show.html | 24 ++---------------- .../coprs_frontend/coprs/templates/coprs/show.html | 28 ++------------------- 3 files changed, 33 insertions(+), 48 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/templates/_helpers.html b/frontend/coprs_frontend/coprs/templates/_helpers.html index 7e76688..ed65088 100644 --- a/frontend/coprs_frontend/coprs/templates/_helpers.html +++ b/frontend/coprs_frontend/coprs/templates/_helpers.html @@ -684,3 +684,32 @@ https://admin.fedoraproject.org/accounts/group/view/%7B%7Bname%7D%7D </tbody> </table> {% endmacro %} + + +{% macro render_project_box(copr) %} +<a href="{{ copr_details_href(copr) }}" class="list-group-item"> + <div> + <h3 class="list-group-item-heading" style="display: inline;"> + {{ copr_name(copr) }} + </h3> + {% if copr.delete_after %} + <small> (temporary project, {{ copr.delete_after_msg }})</small> + {% endif %} + </div> + <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"> + {% for os in copr.active_chroots_grouped %} + <li> + <strong>{{ friendly_os_name(os[0].split()[0], os[0].split()[1]) }}:</strong> + <small> + {% for arch in os[1] %} + {{ arch }}{% if not loop.last %}, {% endif %} + {% endfor %} + </small> + </li> + {% endfor %} + </ul> + </span> +</a> +{% endmacro %} diff --git a/frontend/coprs_frontend/coprs/templates/coprs/group_show.html b/frontend/coprs_frontend/coprs/templates/coprs/group_show.html index f3bafef..a715717 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/group_show.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/group_show.html @@ -2,7 +2,7 @@ {% block title %}Project List{% endblock %} {% block header %}Project List{% endblock %} {% from "_helpers.html" import render_pagination, copr_details_href, copr_name, user_projects_panel %} -{% from "_helpers.html" import recent_builds_panel, task_queue_panel, friendly_os_name %} +{% from "_helpers.html" import recent_builds_panel, task_queue_panel, friendly_os_name, render_project_box %} {%block main_menu_projects %}active{% endblock %} {% block body %} <div class="row"> @@ -15,27 +15,7 @@ <div class="panel panel-default"> <div class="list-group"> {% for copr in coprs %} - <!--copr-project--> - <a href="{{ copr_details_href(copr) }}" class="list-group-item"> - <h3 class="list-group-item-heading"> - {{ copr_name(copr) }} - </h3> - <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"> - {% for os in copr.active_chroots_grouped %} - <li> - <strong>{{ friendly_os_name(os[0].split()[0], os[0].split()[1]) }}:</strong> - <small> - {% for arch in os[1] %} - {{arch}}{% if not loop.last %}, {% endif %} - {%endfor%} - </small> - </li> - {% endfor %} - </ul> - </span> - </a> + {{ render_project_box(copr) }} {% 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 f71361f..e4f4df5 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/show.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/show.html @@ -3,6 +3,7 @@ {% block header %}Project List{% endblock %} {% from "_helpers.html" import render_pagination, copr_details_href, copr_name, user_projects_panel %} {% from "_helpers.html" import recent_builds_panel, task_queue_panel, recent_blog_panel, friendly_os_name %} +{% from "_helpers.html" import render_project_box %} {%block main_menu_projects %}active{% endblock %} {% block body %} <div class="row"> @@ -17,32 +18,7 @@ <div class="panel panel-default"> <div class="list-group"> {% for copr in coprs %} - <!--copr-project--> - <a href="{{ copr_details_href(copr) }}" class="list-group-item"> - <div> - <h3 class="list-group-item-heading" style="display: inline;"> - {{ copr_name(copr) }} - </h3> - {% if copr.delete_after %} - <small> (temporary project, {{ copr.delete_after_msg }})</small> - {% endif %} - </div> - <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"> - {% for os in copr.active_chroots_grouped %} - <li> - <strong>{{ friendly_os_name(os[0].split()[0], os[0].split()[1]) }}:</strong> - <small> - {% for arch in os[1] %} - {{ arch }}{% if not loop.last %}, {% endif %} - {% endfor %} - </small> - </li> - {% endfor %} - </ul> - </span> - </a> + {{ render_project_box(copr) }} {% else %} <p>No projects...</p> {% endfor %}
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@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/%7B%7Bname%7D%7D {% 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) + + +@user_ns.route("/customize-pinned/") +@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) + + +@user_ns.route("/customize-pinned/", methods=["POST"]) +@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]
copr-commits@lists.fedorahosted.org