New Upstream Snapshot - pagure

Ready changes

Summary

Merged new upstream version: 5.13.2+git20230119.1.92678c8+dfsg (was: 5.11.3+dfsg).

Resulting package

Built on 2023-01-31T17:10 (took 39m49s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-snapshots pagure-ciapt install -t fresh-snapshots pagure-docapt install -t fresh-snapshots pagure-ev-serverapt install -t fresh-snapshots pagure-loadjsonapt install -t fresh-snapshots pagure-logcomapt install -t fresh-snapshots pagure-miltersapt install -t fresh-snapshots pagure-mirrorapt install -t fresh-snapshots pagure-webhookapt install -t fresh-snapshots pagure

Diff

diff --git a/PKG-INFO b/PKG-INFO
index 5d1234d..8905704 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,25 +1,24 @@
-Metadata-Version: 1.2
+Metadata-Version: 2.1
 Name: pagure
-Version: 5.11.3
+Version: 5.13.2
 Summary: A light-weight git-centered forge based on pygit2.
 Home-page: https://pagure.io/pagure/
+Download-URL: https://pagure.io/releases/pagure/
 Author: Pierre-Yves Chibon
 Author-email: pingou@pingoured.fr
 Maintainer: Pierre-Yves Chibon
 Maintainer-email: pingou@pingoured.fr
 License: GPLv2+
-Download-URL: https://pagure.io/releases/pagure/
-Description: UNKNOWN
-Platform: UNKNOWN
 Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)
 Classifier: Operating System :: POSIX :: Linux
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
 Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
 Classifier: Topic :: Software Development :: Bug Tracking
 Classifier: Topic :: Software Development :: Version Control
+License-File: LICENSE
diff --git a/README.rst b/README.rst
index 63d1c62..533ee0b 100644
--- a/README.rst
+++ b/README.rst
@@ -122,8 +122,8 @@ The Vagrant pagure doesn't have its own log file, use ``journalctl -f`` to
 show the pagure output. The verbosity can be configured in the pagure config file
 with the ``LOGGING`` parameter.
 
-Running the unit-tests
-**********************
+Running the unit-tests in container
+***********************************
 
 To run the unit-tests, there is container available with all the dependencies needed.
 
@@ -160,6 +160,43 @@ You can also get `run-tests-container` help ::
 
     $ ./dev/run-tests-container.py --help
 
+Run the tests on your own development branch in your fork ::
+
+    $ ./dev/run-tests-container.py --repo https://pagure.io/forks/<username>/pagure.git --branch <name of branch to test>
+
+  .. note:: This run could take pretty long to finish and there isn't any useful summary.
+            So it's better to redirect the output to some file. You can use `tee` for this.
+
+ 
+Running the unit-tests in tox
+*****************************
+
+You can run the tests using tox. This allows you to run the tests on local version of the code.
+
+  .. note:: This way of running tests could help you test your local changes,
+            but the output could be different then from the containerized tests.
+            Always check your branch after push with containerized tests as well.
+
+* Install the needed system libraries::
+
+     sudo dnf install libgit2-devel redis gcc tox python-alembic
+
+
+  .. note:: You can also install any missing python interpreter.
+            For example `sudo dnf install python35`
+
+* Run the whole test suite::
+
+     tox
+
+* Or just single environment::
+
+     tox -e py39
+
+* Or single module::
+
+     tox tests/test_style.py
+
 Manually
 ^^^^^^^^
 
@@ -239,7 +276,6 @@ Manually
 
 This will launch the application at http://127.0.0.1:5000
 
-
 * To run unit-tests on pagure
 
   * Install the dependencies::
@@ -248,16 +284,12 @@ This will launch the application at http://127.0.0.1:5000
 
   * Run it::
 
-      python runtests.py run
-
-   You may use::
-
-      python runtests.py --help
-
-   to check other options supported or read the source code ;-)
+      pytest tests/
 
     .. note:: While testing for worker tasks, pagure uses celery in /usr/bin/
             Celery then looks for eventlet (which we use for testing only) at
             system level and not in virtual environment. You will need to
             install eventlet outside of your virtual environment if you are
             using one.
+
+    .. note:: This will also work in vagrant.
diff --git a/UPGRADING.rst b/UPGRADING.rst
index 81a3e4e..07d5962 100644
--- a/UPGRADING.rst
+++ b/UPGRADING.rst
@@ -1,6 +1,33 @@
 Upgrading Pagure
 ================
 
+From 5.12 to 5.13
+-----------------
+
+The 5.13 release does not contain any database schema changes.
+
+
+From 5.12 to 5.12.1
+-------------------
+
+The 5.12.1 release contains a database schema updates, so:
+
+* Update the data schema using alembic: ``alembic upgrade head``
+
+(As usual, do your backups before).
+
+
+From 5.11 to 5.12
+-----------------
+
+The 5.12 release does not contain any database schema changes.
+
+New configuration key added (in 5.12):
+
+* NOGITHOOKS (must not be touched)
+* ALLOW_USER_REGISTRATION
+* GIT_DEFAULT_BRANCH
+
 
 From 5.10 to 5.11
 -----------------
@@ -15,6 +42,14 @@ New configuration key added (in 5.11.2):
 
 * LOGGING_GIT_HOOKS
 
+.. warning:: When upgrading MySQL database you can encounter issue with
+       incompatible columns. In this case try to check the collation of the
+       mentioned column by ``show full columns from <table>;`` in ``mysql``.
+       If the collation is something else then `utf8` you need to convert
+       your database to `utf8` first. See
+       `this guide <https://stackoverflow.com/questions/1294117/how-to-change-collation-of-database-table-column>`_
+       for how to do it or if you have SQL script for db setup,
+       you can change it directly in the script.
 
 From 5.9 to 5.10
 ----------------
@@ -342,7 +377,7 @@ The release 3.11 brings some changes to the database schema.
 
 In addition, if you are deploying pagure with fedmsg support you had to set
 fedmsg to the
-`active <http://www.fedmsg.com/en/stable/publishing/#publishing-through-a-relay>`_
+`active <https://fedmsg.readthedocs.io/en/stable/publishing/#publishing-through-a-relay>`_
 mode for the workers to be able to send fedmsg messages. This is now the
 default and forced configuration.
 
diff --git a/alembic/versions/c0bffa4e8fbc_token_if_in_commit_flag_can_be_null.py b/alembic/versions/c0bffa4e8fbc_token_if_in_commit_flag_can_be_null.py
new file mode 100644
index 0000000..daac18c
--- /dev/null
+++ b/alembic/versions/c0bffa4e8fbc_token_if_in_commit_flag_can_be_null.py
@@ -0,0 +1,35 @@
+"""token_if in commit flag can be null
+
+Revision ID: c0bffa4e8fbc
+Revises: 2b39a728a38f
+Create Date: 2021-01-08 11:12:45.380762
+
+"""
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'c0bffa4e8fbc'
+down_revision = '2b39a728a38f'
+
+
+def upgrade():
+    op.alter_column(
+        'commit_flags',
+        column_name='token_id',
+        existing_type=sa.String(64),
+        nullable=False,
+        existing_nullable=True
+    )
+
+
+def downgrade():
+    op.alter_column(
+        'commit_flags',
+        column_name='token_id',
+        existing_type=sa.String(64),
+        nullable=True,
+        existing_nullable=False
+    )
diff --git a/debian/changelog b/debian/changelog
index 1af88ed..c1cd06e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+pagure (5.13.2+git20230119.1.92678c8+dfsg-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Tue, 31 Jan 2023 16:44:48 -0000
+
 pagure (5.11.3+dfsg-2) unstable; urgency=medium
 
   [ Sergio Durigan Junior ]
diff --git a/debian/patches/0002-Fix-Python-shebang-for-various-scripts.patch b/debian/patches/0002-Fix-Python-shebang-for-various-scripts.patch
index 13ad993..0ad7cbb 100644
--- a/debian/patches/0002-Fix-Python-shebang-for-various-scripts.patch
+++ b/debian/patches/0002-Fix-Python-shebang-for-various-scripts.patch
@@ -29,71 +29,71 @@ For more info: https://pagure.io/pagure/issue/4725
  tests/test_style.py                             | 2 +-
  19 files changed, 19 insertions(+), 19 deletions(-)
 
-diff --git a/createdb.py b/createdb.py
-index 3860b51..5bbcd14 100644
---- a/createdb.py
-+++ b/createdb.py
+Index: pagure.git/createdb.py
+===================================================================
+--- pagure.git.orig/createdb.py
++++ pagure.git/createdb.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  
  from __future__ import print_function, unicode_literals, absolute_import
  
-diff --git a/files/aclchecker.py b/files/aclchecker.py
-index ee5cb01..1657123 100644
---- a/files/aclchecker.py
-+++ b/files/aclchecker.py
+Index: pagure.git/files/aclchecker.py
+===================================================================
+--- pagure.git.orig/files/aclchecker.py
++++ pagure.git/files/aclchecker.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  # -*- coding: utf-8 -*-
  
  """
-diff --git a/files/api_key_expire_mail.py b/files/api_key_expire_mail.py
-index a9befb9..15bd0f6 100755
---- a/files/api_key_expire_mail.py
-+++ b/files/api_key_expire_mail.py
+Index: pagure.git/files/api_key_expire_mail.py
+===================================================================
+--- pagure.git.orig/files/api_key_expire_mail.py
++++ pagure.git/files/api_key_expire_mail.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  
  from __future__ import print_function, absolute_import
  import os
-diff --git a/files/emoji_clean_json.py b/files/emoji_clean_json.py
-index 8a91fa4..25fafbd 100644
---- a/files/emoji_clean_json.py
-+++ b/files/emoji_clean_json.py
+Index: pagure.git/files/emoji_clean_json.py
+===================================================================
+--- pagure.git.orig/files/emoji_clean_json.py
++++ pagure.git/files/emoji_clean_json.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  
  from __future__ import print_function, absolute_import
  import json
-diff --git a/files/keyhelper.py b/files/keyhelper.py
-index c949c10..9c25a75 100644
---- a/files/keyhelper.py
-+++ b/files/keyhelper.py
+Index: pagure.git/files/keyhelper.py
+===================================================================
+--- pagure.git.orig/files/keyhelper.py
++++ pagure.git/files/keyhelper.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  # -*- coding: utf-8 -*-
  
  """
-diff --git a/files/mirror_project_in.py b/files/mirror_project_in.py
-index 7a6a306..616ddab 100644
---- a/files/mirror_project_in.py
-+++ b/files/mirror_project_in.py
+Index: pagure.git/files/mirror_project_in.py
+===================================================================
+--- pagure.git.orig/files/mirror_project_in.py
++++ pagure.git/files/mirror_project_in.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  
  from __future__ import print_function, absolute_import
  import os
-diff --git a/files/pagure_mirror_project_in.service b/files/pagure_mirror_project_in.service
-index 4a3a3ee..71f35da 100644
---- a/files/pagure_mirror_project_in.service
-+++ b/files/pagure_mirror_project_in.service
-@@ -3,7 +3,7 @@ Description=Pagure service to mirror in projects
+Index: pagure.git/files/pagure_mirror_project_in.service
+===================================================================
+--- pagure.git.orig/files/pagure_mirror_project_in.service
++++ pagure.git/files/pagure_mirror_project_in.service
+@@ -9,7 +9,7 @@ Description=Pagure service to mirror in
  Documentation=https://pagure.io/pagure
  
  [Service]
@@ -102,120 +102,120 @@ index 4a3a3ee..71f35da 100644
  Environment="PAGURE_CONFIG=/etc/pagure/pagure.cfg"
  Type=simple
  User=git
-diff --git a/pagure-ev/pagure_stream_server.py b/pagure-ev/pagure_stream_server.py
-index d1aeb00..f556916 100644
---- a/pagure-ev/pagure_stream_server.py
-+++ b/pagure-ev/pagure_stream_server.py
+Index: pagure.git/pagure-ev/pagure_stream_server.py
+===================================================================
+--- pagure.git.orig/pagure-ev/pagure_stream_server.py
++++ pagure.git/pagure-ev/pagure_stream_server.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  
  """
   (c) 2015-2017 - Copyright Red Hat Inc
-diff --git a/pagure-milters/comment_email_milter.py b/pagure-milters/comment_email_milter.py
-index 54a2b6f..4ee40e6 100644
---- a/pagure-milters/comment_email_milter.py
-+++ b/pagure-milters/comment_email_milter.py
+Index: pagure.git/pagure-milters/comment_email_milter.py
+===================================================================
+--- pagure.git.orig/pagure-milters/comment_email_milter.py
++++ pagure.git/pagure-milters/comment_email_milter.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  # -*- coding: utf-8 -*-
  
  # Milter calls methods of your class at milter events.
-diff --git a/pagure/hooks/files/git_multimail_upstream.py b/pagure/hooks/files/git_multimail_upstream.py
-index 0780563..65b165f 100755
---- a/pagure/hooks/files/git_multimail_upstream.py
-+++ b/pagure/hooks/files/git_multimail_upstream.py
+Index: pagure.git/pagure/hooks/files/git_multimail_upstream.py
+===================================================================
+--- pagure.git.orig/pagure/hooks/files/git_multimail_upstream.py
++++ pagure.git/pagure/hooks/files/git_multimail_upstream.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  
  __version__ = "1.4.0"
  
-diff --git a/pagure/hooks/files/hookrunner b/pagure/hooks/files/hookrunner
-index 0708665..9302618 100755
---- a/pagure/hooks/files/hookrunner
-+++ b/pagure/hooks/files/hookrunner
+Index: pagure.git/pagure/hooks/files/hookrunner
+===================================================================
+--- pagure.git.orig/pagure/hooks/files/hookrunner
++++ pagure.git/pagure/hooks/files/hookrunner
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  # -*- coding: utf-8 -*-
  
  """
-diff --git a/pagure/hooks/files/post-receive b/pagure/hooks/files/post-receive
-index 0708665..9302618 100755
---- a/pagure/hooks/files/post-receive
-+++ b/pagure/hooks/files/post-receive
+Index: pagure.git/pagure/hooks/files/post-receive
+===================================================================
+--- pagure.git.orig/pagure/hooks/files/post-receive
++++ pagure.git/pagure/hooks/files/post-receive
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  # -*- coding: utf-8 -*-
  
  """
-diff --git a/pagure/hooks/files/pre-receive b/pagure/hooks/files/pre-receive
-index 0708665..9302618 100755
---- a/pagure/hooks/files/pre-receive
-+++ b/pagure/hooks/files/pre-receive
+Index: pagure.git/pagure/hooks/files/pre-receive
+===================================================================
+--- pagure.git.orig/pagure/hooks/files/pre-receive
++++ pagure.git/pagure/hooks/files/pre-receive
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  # -*- coding: utf-8 -*-
  
  """
-diff --git a/pagure/hooks/files/repospannerhook b/pagure/hooks/files/repospannerhook
-index 80e0a90..22fac80 100755
---- a/pagure/hooks/files/repospannerhook
-+++ b/pagure/hooks/files/repospannerhook
+Index: pagure.git/pagure/hooks/files/repospannerhook
+===================================================================
+--- pagure.git.orig/pagure/hooks/files/repospannerhook
++++ pagure.git/pagure/hooks/files/repospannerhook
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  # -*- coding: utf-8 -*-
  
  """
-diff --git a/setup.py b/setup.py
-index c7d9aa6..5fa00ea 100644
---- a/setup.py
-+++ b/setup.py
+Index: pagure.git/setup.py
+===================================================================
+--- pagure.git.orig/setup.py
++++ pagure.git/setup.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  
  """
  Setup script
-diff --git a/tests/test_fnmatch.py b/tests/test_fnmatch.py
-index 18e1eff..295a8f5 100644
---- a/tests/test_fnmatch.py
-+++ b/tests/test_fnmatch.py
+Index: pagure.git/tests/test_fnmatch.py
+===================================================================
+--- pagure.git.orig/tests/test_fnmatch.py
++++ pagure.git/tests/test_fnmatch.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  # -*- coding: utf-8 -*-
  
  """
-diff --git a/tests/test_pagure_flask_ui_issues_acl_checks.py b/tests/test_pagure_flask_ui_issues_acl_checks.py
-index f8bc550..b2f027f 100644
---- a/tests/test_pagure_flask_ui_issues_acl_checks.py
-+++ b/tests/test_pagure_flask_ui_issues_acl_checks.py
+Index: pagure.git/tests/test_pagure_flask_ui_issues_acl_checks.py
+===================================================================
+--- pagure.git.orig/tests/test_pagure_flask_ui_issues_acl_checks.py
++++ pagure.git/tests/test_pagure_flask_ui_issues_acl_checks.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  # coding=utf-8
  
  """
-diff --git a/tests/test_stream_server.py b/tests/test_stream_server.py
-index 0139094..e2c8d21 100644
---- a/tests/test_stream_server.py
-+++ b/tests/test_stream_server.py
+Index: pagure.git/tests/test_stream_server.py
+===================================================================
+--- pagure.git.orig/tests/test_stream_server.py
++++ pagure.git/tests/test_stream_server.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
  # -*- coding: utf-8 -*-
  
  """
-diff --git a/tests/test_style.py b/tests/test_style.py
-index 717b814..9e37ede 100644
---- a/tests/test_style.py
-+++ b/tests/test_style.py
+Index: pagure.git/tests/test_style.py
+===================================================================
+--- pagure.git.orig/tests/test_style.py
++++ pagure.git/tests/test_style.py
 @@ -1,4 +1,4 @@
 -#!/usr/bin/env python
 +#!/usr/bin/python3
diff --git a/debian/patches/0003-Adjust-path-of-aclchecker.py.patch b/debian/patches/0003-Adjust-path-of-aclchecker.py.patch
index 78f0d4c..0416e16 100644
--- a/debian/patches/0003-Adjust-path-of-aclchecker.py.patch
+++ b/debian/patches/0003-Adjust-path-of-aclchecker.py.patch
@@ -10,11 +10,11 @@ install the script (/usr/share/pagure).
  pagure/default_config.py | 2 +-
  1 file changed, 1 insertion(+), 1 deletion(-)
 
-diff --git a/pagure/default_config.py b/pagure/default_config.py
-index 9693eab..ebd6e64 100644
---- a/pagure/default_config.py
-+++ b/pagure/default_config.py
-@@ -572,7 +572,7 @@ SSH_KEYS_USERNAME_EXPECT = None
+Index: pagure.git/pagure/default_config.py
+===================================================================
+--- pagure.git.orig/pagure/default_config.py
++++ pagure.git/pagure/default_config.py
+@@ -593,7 +593,7 @@ SSH_KEYS_USERNAME_EXPECT = None
  # Arguments to add to the SSH keys, possible replacements:
  # %(username)s: username owning this key
  SSH_KEYS_OPTIONS = (
diff --git a/debian/patches/0004-Adjust-flask_app.py-to-locate-template-static-and-th.patch b/debian/patches/0004-Adjust-flask_app.py-to-locate-template-static-and-th.patch
index 0823f85..0308562 100644
--- a/debian/patches/0004-Adjust-flask_app.py-to-locate-template-static-and-th.patch
+++ b/debian/patches/0004-Adjust-flask_app.py-to-locate-template-static-and-th.patch
@@ -14,10 +14,10 @@ Last-Updated: 2020-08-09
  pagure/flask_app.py | 11 ++++++++---
  1 file changed, 8 insertions(+), 3 deletions(-)
 
-diff --git a/pagure/flask_app.py b/pagure/flask_app.py
-index efeb564..f755a72 100644
---- a/pagure/flask_app.py
-+++ b/pagure/flask_app.py
+Index: pagure.git/pagure/flask_app.py
+===================================================================
+--- pagure.git.orig/pagure/flask_app.py
++++ pagure.git/pagure/flask_app.py
 @@ -164,9 +164,12 @@ def create_app(config=None):
          app.register_blueprint(blueprint)
  
diff --git a/doc/changelog.rst b/doc/changelog.rst
index 4154e0a..0b5e658 100644
--- a/doc/changelog.rst
+++ b/doc/changelog.rst
@@ -3,6 +3,98 @@ Changelog
 
 This document records all notable changes to `Pagure <https://pagure.io>`_.
 
+5.13.2 (2021-01-29)
+-------------------
+- Fix broken pagination of group API (Lukas Brabec and František Zatloukal)
+- Fixing the alias url in the examples (Mohan Boddu)
+- Pull in upstream fix for apostrophes from highlightjs-rpm-specfile (David Auer)
+- Improve logging when trying to interract with a git repo via http(s)
+
+
+5.13.1 (2021-01-29)
+-------------------
+- Add the api_project_hascommit endpoint to the API doc
+- Do not return a 500 error when the OpenID provider doesn't provide an email
+- Fix bug in the default hook
+
+
+5.13.0 (2021-01-19)
+-------------------
+- When failing to find a git repo, log where pagure looked
+- Get the default branch of the target repo when linking for new PR
+- Add an hascommit API endpoint
+- Fixing sample input and output for alias related api (Mohan Boddu)
+- Add missing API endpoints related to git aliases and re-order a little
+- Add support for chardet 4.0+
+- Fix support for cchardet
+
+
+5.12.1 (2021-01-08)
+-------------------
+- Block chardet 4.0, we're not compatible with it yet
+- Be consistent in the messages sent and with the schemas defined in
+  pagure-schemas (0.0.4+)
+- Make the token_id column of the commit_flags table nullable
+
+
+5.12.0 (2021-01-06)
+-------------------
+
+/!\ the PR flag API is now creating Commit flag on the commit at the top of the
+pull-request.
+
+
+- Display real line numbers on pull request's diff view (Julen Landa Alustiza)
+- Show the assignee's avatar on the board
+- Allow setting a status as closing even if the project has no close_status
+- Include the assignee in the list of people notified on a ticket/PR
+- Add orphaning reason on the dist-git theme (Michal Konečný)
+- Adjust the way we generate humanized dates so we provide the humanized date
+  as well as the actual date when hovering over (Julen Landa Alustiza)
+- When a file a detected as a binary file, return the raw file
+- Allow using the modifyacl API endpoint to remove groups from a project
+- Add a note that repo_from* argument are mandatory in some situations when
+  opening a Pull-Request from the API
+- Increase the list of running pagure instances in the documentation (Neal Gompa)
+- Remove fenced code block when checking mention (Michael Scherer)
+- Add support for using cchardet to detect files' encoding
+- Show the default branch in the project overview page
+- Send appropriate SMTP status codes and error messages in the milter. (Björn
+  Persson)
+- Report an error if a message ID isn't recognized by the milter. (Björn Persson)
+- Add support for disabling user registration (Neal Gompa)
+- Add a way to make the stats view on more than one year (if you know how to)
+- Encode the data passed onto the mail hook so it is of bytes type
+- Reverse out of order instructions for new repos (Jerry James)
+- Split the list of branches into two lists active/inactive in dist-git
+- Rework the "My PR" page so it does not pull so many info at once
+- Include the date of the last mirroring process in the logs
+- Forward the username when updating the pull-request
+- Add pagination to group API (Michal Konečný)
+- When returning the commits flags in the API, returned them by update date
+- Change the PR flag API endpoints to use commit flags
+- Only show the subscribers list on demand
+- Improve the message showns when a new mirrored project is created
+- When editing the issue's description sent the html of it to the SSE server
+- Add an update-acls action to pagure-admin
+- Add support for AAA system sending SSH keys encoded in base64
+- Allow deleting the master branch when it is not the default branch
+- Allow people with a fork to have a working drop-down for opening new PRs
+- Fix handling "false" when editing project's options via the API (Bernhard M.
+  Wiedemann)
+- Ensure a fork project has the same default branch as its parent
+- Allow to specify a default branch for all projects hosted on an instance
+- Add support for pagure-messages
+- Add a notification for when a group is removed from a project
+- When checking if messages were sent via a rebase, do not run the git hooks
+- Make the API endpoint to update project's options accept JSON
+- Add a full_url to the JSON representation of our main objects
+- Ensure the author in git commit notifications follow the expected format
+- Add support for git branch aliases
+- Update the vagrant development environment
+- Allow updating the target branch when editing a PR
+
+
 5.11.3 (2020-08-11)
 -------------------
 
diff --git a/doc/configuration.rst b/doc/configuration.rst
index 735e378..9b6020b 100644
--- a/doc/configuration.rst
+++ b/doc/configuration.rst
@@ -44,7 +44,7 @@ Examples values:
 ::
 
     DB_URL = 'mysql://user:pass@host/db_name'
-    DB_URL = 'postgres://user:pass@host/db_name'
+    DB_URL = 'postgresql://user:pass@host/db_name'
     DB_URL = 'sqlite:////var/tmp/pagure_dev.sqlite'
 
 Defaults to ``sqlite:////var/tmp/pagure_dev.sqlite``
@@ -105,10 +105,10 @@ This configuration key is used to point celery to the broker to use. This
 is the broker that is used to communicate between the web application and
 its workers.
 
-Defaults to: ``'redis://%s' % APP.config['REDIS_HOST']``
+Defaults to: ``"redis://%s:%d/%d" % (pagure_config["REDIS_HOST"], pagure_config["REDIS_PORT"], pagure_config["REDIS_DB"])``
 
-.. note:: See the :ref:`redis-section` for the ``REDIS_HOST`` configuration
-          key
+.. note:: See the :ref:`redis-section` for the ``REDIS_HOST``, ``REDIS_PORT``
+          and ``REDIS_DB``configuration keys
 
 
 Repo Directories
@@ -1106,7 +1106,7 @@ PAGURE_AUTH
 This configuration key specifies which authentication method to use.
 Valid options are ``fas``, ``openid``, ``oidc``, or ``local``.
 
-* ``fas`` uses the Fedora Account System `FAS <https://admin.fedoraproject.org/accounts>`
+* ``fas`` uses the Fedora Account System `FAS <https://accounts.fedoraproject.org>`
   to provide user authentication and enforces that users sign the FPCA.
 
 * ``openid`` uses OpenID authentication.  Any provider may be used by
@@ -1117,6 +1117,7 @@ Valid options are ``fas``, ``openid``, ``oidc``, or ``local``.
   the configuration options starting with ``OIDC_`` (see below) to be provided.
 
 * ``local`` causes pagure to use the local pagure database for user management.
+  User registration can be disabled with the ALLOW_USER_REGISTRATION configuration key.
 
 Defaults to: ``local``.
 
@@ -1315,7 +1316,7 @@ FEDMSG_NOTIFICATIONS
 ~~~~~~~~~~~~~~~~~~~~
 
 This configuration key can be used to turn on or off notifications via `fedmsg
-<http://www.fedmsg.com/>`_.
+<https://fedmsg.readthedocs.io/>`_.
 
 Defaults to: ``False``.
 
@@ -1332,7 +1333,7 @@ Defaults to: ``False``.
 ALWAYS_FEDMSG_ON_COMMITS
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
-This configuration key can be used to enforce `fedmsg <http://www.fedmsg.com/>`_
+This configuration key can be used to enforce `fedmsg <https://fedmsg.readthedocs.io/>`_
 notifications on commits made on all projects in a pagure instance.
 
 Defaults to: ``True``.
@@ -1784,6 +1785,18 @@ If turned off, users are managed outside of pagure.
 Defaults to: ``True``
 
 
+ALLOW_USER_REGISTRATION
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This configuration key can be used to turn on or off user registration
+(that is, the ability for users to create an account) in this pagure instance.
+If turned off, user accounts cannot be created through the UI or API.
+Currently, this key only applies to pagure instances configured with the ``local``
+authentication backend and has no effect with the other authentication backends.
+
+Defaults to: ``True``
+
+
 SESSION_COOKIE_NAME
 ~~~~~~~~~~~~~~~~~~~
 
@@ -1896,8 +1909,41 @@ existing git tags via the API.
 When set to ``False``, this essentially makes the API ignore whether the
 ``force`` argument is set or not.
 
+Defaults to: ``True``
+
+
+PAGURE_PLUGINS_CONFIG
+~~~~~~~~~~~~~~~~~~~~~~
+
+This option can be used to specify the configuration file used for loading
+plugins. It is not set by default, instead if must be declared explicitly.
+Also see the documentation on plugins at :ref:`plugins`.
+
+
+GIT_DEFAULT_BRANCH
+~~~~~~~~~~~~~~~~~~
+
+This configuration key allows to specify the default branch configured upon
+project creation. The default branch can be specified by the user upon project
+creation but if the user does not specify any branch, this branch name will be
+used.
 
-Default to: ``True``
+Defaults to: ``None`` (which results in the default branch being ``master``).
+
+
+PR_WARN_CHARACTERS
+~~~~~~~~~~~~~~~~~~
+
+List of characters that triggers a warning to the users when met in a commit of
+a pull-request (each commit being made checked).
+
+Defaults to:
+::
+
+    set([
+        chr(0x202a), chr(0x202b), chr(0x202c), chr(0x202d), chr(0x202e),
+        chr(0x2066), chr(0x2067), chr(0x2068), chr(0x2069)
+    ])
 
 
 RepoSpanner Options
@@ -2045,6 +2091,7 @@ SSH_COMMAND_NON_REPOSPANNER
 The command to run if a repository is not on repospanner when aclchecker is in use.
 
 
+
 MQTT Options
 ------------
 
@@ -2165,12 +2212,19 @@ notifications on commits made on all projects in a pagure instance.
 Defaults to: ``False``.
 
 
-PAGURE_PLUGINS_CONFIG
-~~~~~~~~~~~~~~~~~~~~~~
+NOGITHOOKS
+~~~~~~~~~~
+
+This configuration key should not be touched. It is used in the test suite as a
+way to prevent all the git hooks from running (which includes checking if the
+user is allowed to push). Using this mechanism we are able to check some
+behavior in the test suite that in a deployed pagure instance are happening in
+a different process.
+
+**Do not change this option in production**
+
+Defaults to: ``None``.
 
-This option can be used to specify the configuration file used for loading
-plugins. It is not set by default, instead if must be declared explicitly.
-Also see the documentation on plugins at :ref:`plugins`.
 
 
 Deprecated configuration keys
diff --git a/doc/contributors.rst b/doc/contributors.rst
index e35e170..33de076 100644
--- a/doc/contributors.rst
+++ b/doc/contributors.rst
@@ -3,15 +3,15 @@ Contributors to pagure
 
 Pagure would be nothing without its contributors.
 
-On May 14, 2019 (release 5.10.0) the list looks as follow:
+On February 10, 2021 (release 5.13.2) the list looks as follow:
 
 =================  ===========
 Number of commits  Contributor
 =================  ===========
-  6799              Pierre-Yves Chibon <pingou@pingoured.fr>
+  6895              Pierre-Yves Chibon <pingou@pingoured.fr>
    328              Ryan Lerch <rlerch@redhat.com>
    172              Vivek Anand <vivekanand1101@gmail.com>
-   144              Julen Landa Alustiza <jlanda@fedoraproject.org>
+   147              Julen Landa Alustiza <jlanda@fedoraproject.org>
    139              farhaanbukhsh <farhaan.bukhsh@gmail.com>
    134              Clement Verna <cverna@tutanota.com>
    133              Patrick Uiterwijk <puiterwijk@redhat.com>
@@ -21,14 +21,14 @@ Number of commits  Contributor
     59              Johan Cwiklinski <johan@x-tnd.be>
     52              Karsten Hopp <karsten@redhat.com>
     47              Mark Reynolds <mreynolds@redhat.com>
-    36              Neal Gompa <ngompa13@gmail.com>
+    39              Neal Gompa <ngompa13@gmail.com>
     33              Lubomír Sedlář <lsedlar@redhat.com>
     32              Matt Prahl <mprahl@redhat.com>
     32              Pradeep CE (cep) <breathingcode@gmail.com>
     25              Lubomír Sedlář <lubomir.sedlar@gmail.com>
     23              rahul Bajaj <you@example.com>
+    21              Aurélien Bompard <aurelien@bompard.org>
     20              Jeremy Cline <jeremy@jcline.org>
-    19              Aurélien Bompard <aurelien@bompard.org>
     19              Fabien Boucher <fboucher@redhat.com>
     19              Gaurav Kumar <aavrug@gmail.com>
     19              Lenka Segura <lenka@sepu.cz>
@@ -44,14 +44,16 @@ Number of commits  Contributor
     13              Martin Basti <mbasti@redhat.com>
     13              Mathieu Bridon <bochecha@fedoraproject.org>
     11              Shengjing Zhu <zsj950618@gmail.com>
+     9              Björn Persson <Bjorn@Rombobjörn.se>
      9              Michael Watters <michael.watters@dart.biz>
      9              mprahl <mprahl@redhat.com>
      8              Lei Yang <yltt1234512@gmail.com>
+     8              Michael Scherer <misc@redhat.com>
      8              Paul W. Frields <stickster@gmail.com>
      7              René Genz <liebundartig@freenet.de>
      7              Sergio Durigan Junior <sergiodj@sergiodj.net>
      7              zPlus <zplus@peers.community>
-     6              Michael Scherer <misc@redhat.com>
+     6              Michal Konečný <mkonecny@redhat.com>
      6              ymdatta <ymdatta@protonmail.com>
      5              Fabio Valentini <decathorpe@gmail.com>
      5              Lukas Holecek <hluk@email.cz>
@@ -63,11 +65,11 @@ Number of commits  Contributor
      5              vanzhiganov <vanzhiganov@ya.ru>
      5              yangl1996 <yltt1234512@gmail.com>
      4              Alex Gleason <alex@alexgleason.me>
+     4              Andrew Engelbrecht <andrew@engelbrecht.io>
      4              Eric Barbour <ebarbour@redhat.com>
      4              Maciej Lasyk <maciek@lasyk.info>
      4              clime <clime@redhat.com>
      3              Akanksha <akanksha_mishra01@yahoo.com>
-     3              Andrew Engelbrecht <andrew@engelbrecht.io>
      3              Ankush Behl <cloudbehl@gmail.com>
      3              Anthony Lackey <alackey96@gmail.com>
      3              Chenxiong Qi <cqi@redhat.com>
@@ -77,7 +79,6 @@ Number of commits  Contributor
      3              Jan Pokorný <jpokorny@redhat.com>
      3              Jason Tibbitts <tibbs@math.uh.edu>
      3              Kushal Khandelwal <kushal124@gmail.com>
-     3              Michal Konečný <mkonecny@redhat.com>
      3              Miro Hrončok <miro@hroncok.cz>
      3              Pedro Lima <pedro.lima@gmail.com>
      3              Pierre-YvesChibon <pingou@fedoraproject.org>
@@ -91,14 +92,16 @@ Number of commits  Contributor
      3              tenstormavi <avi.avinash3008@gmail.com>
      2              Akshay Gaikwad <akgaikwad001@gmail.com>
      2              Anatoli Babenia <anatoli@rainforce.org>
-     2              Björn Persson <Bjorn@Rombobjörn.se>
+     2              Bernhard M. Wiedemann <bwiedemann@suse.de>
      2              Carlos Mogas da Silva <r3pek@r3pek.org>
      2              Daniel Mach <dmach@redhat.com>
      2              Fabian Arrotin <fabian.arrotin@arrfab.net>
+     2              František Zatloukal <fzatlouk@redhat.com>
      2              Hervé Beraud <hberaud@redhat.com>
      2              Kamil Páral <kparal@redhat.com>
      2              Luis Guzman <ark@switnet.org>
      2              MR <mrx@mailinator.com>
+     2              Mohan Boddu <mboddu@bhujji.com>
      2              Neha Kandpal <iec2015048@iiita.ac.in>
      2              Nuno Maltez <nuno@cognitiva.com>
      2              Ompragash <om.apsara@gmail.com>
@@ -115,6 +118,7 @@ Number of commits  Contributor
      2              bruno <bruno@wolff.to>
      2              dhrish20 <dhrish20@gmail.com>
      2              hellcp <hellcp@opensuse.org>
+     2              siddharthvipul <siddharthvipul1@gmail.com>
      2              yadneshk <yadnesh45@gmail.com>
      2              “AnjaliPardeshi” <“anjalipardeshi92@gmail.com”>
      1              Akanksha Mishra <akanksha_mishra01@yahoo.com>
@@ -122,12 +126,14 @@ Number of commits  Contributor
      1              Alexander Scheel <ascheel@redhat.com>
      1              Alois Mahdal <amahdal@redhat.com>
      1              Amol Kahat <akahat@redhat.com>
+     1              Andrew Engelbrecht <andrew@sol.lan>
      1              Anthony Lackey <alackey@localhost.localdomain>
      1              Antoni Segura Puimedon <celebdor@gmail.com>
      1              Arti Laddha <artiladdha53@gmail.com>
      1              Brian (bex) Exelbierd <bex@pobox.com>
      1              Carl George <carl@george.computer>
      1              Charelle Collett <ccollett@redhat.com>
+     1              David Auer <dreua@posteo.de>
      1              David Caro <dcaroest@redhat.com>
      1              Devesh Kumar Singh <deveshkusingh@gmail.com>
      1              Eashan <eashankadam@gmail.com>
@@ -137,11 +143,14 @@ Number of commits  Contributor
      1              Haikel Guemar <hguemar@fedoraproject.org>
      1              Hazel Smith <hazel@hazelesque.uk>
      1              Jeremy Cline <jcline@redhat.com>
+     1              Jerry James <loganjerry@gmail.com>
      1              Jingjing Shao <sanri.ok@163.com>
      1              John Florian <jflorian@doubledog.org>
      1              Jun Aruga <jaruga@redhat.com>
      1              Ken Dreyer <kdreyer@redhat.com>
+     1              Koichi MATSUMOTO <mzch@me.com>
      1              Kunaal Jain <kunaalus@gmail.com>
+     1              Lukas Brabec <lbrabec@redhat.com>
      1              Mary Kate Fain <mk@marykatefain.com>
      1              Mathew Robinson <mathew.robinson3114@gmail.com>
      1              Michal Srb <michal@redhat.com>
diff --git a/doc/install.rst b/doc/install.rst
index afa1841..a299d04 100644
--- a/doc/install.rst
+++ b/doc/install.rst
@@ -101,19 +101,19 @@ To install pagure via this mechanism simply follow these steps:
 
 # Install the additional files as follow:
 
-+------------------------------+------------------------------------------+
-|         Source               |             Destination                  |
-+=============================+===========================================+
-| ``files/pagure.cfg.sample``  | ``/etc/pagure/pagure.cfg``               |
-+------------------------------+------------------------------------------+
-| ``files/alembic.ini``        | ``/etc/pagure/alembic.ini``              |
-+------------------------------+------------------------------------------+
-| ``files/pagure.conf``        | ``/etc/httpd/conf.d/pagure.conf``        |
-+------------------------------+------------------------------------------+
-| ``files/pagure.wsgi``        | ``/usr/share/pagure/pagure.wsgi``        |
-+------------------------------+------------------------------------------+
-| ``createdb.py``              | ``/usr/share/pagure/pagure_createdb.py`` |
-+------------------------------+------------------------------------------+
++------------------------------------+------------------------------------------+
+|         Source                     |             Destination                  |
++====================================+==========================================+
+| ``files/pagure.cfg.sample``        | ``/etc/pagure/pagure.cfg``               |
++------------------------------------+------------------------------------------+
+| ``files/alembic.ini``              | ``/etc/pagure/alembic.ini``              |
++------------------------------------+------------------------------------------+
+| ``files/pagure-apache-httpd.conf`` | ``/etc/httpd/conf.d/pagure.conf``        |
++------------------------------------+------------------------------------------+
+| ``files/pagure.wsgi``              | ``/usr/share/pagure/pagure.wsgi``        |
++------------------------------------+------------------------------------------+
+| ``createdb.py``                    | ``/usr/share/pagure/pagure_createdb.py`` |
++------------------------------------+------------------------------------------+
 
 
 
@@ -160,7 +160,7 @@ If installed by RPM, you will find an example apache configuration file
 at: ``/etc/httpd/conf.d/pagure.conf``.
 
 If not installed by RPM, the example file is present in the sources at:
-``files/pagure.conf``.
+``files/pagure-apache-httpd.conf``.
 
 Adjust it for your needs.
 
diff --git a/doc/overview.rst b/doc/overview.rst
index 81af321..e732993 100644
--- a/doc/overview.rst
+++ b/doc/overview.rst
@@ -110,7 +110,7 @@ Pagure web-hook Server
 
 Sends notifications to third party services using POST http requests.
 
-This is the second notifications system in pagure with `fedmsg <http://fedmsg.com/>`_.
+This is the second notifications system in pagure with `fedmsg <https://fedmsg.readthedocs.io/>`_.
 These notifications are running on their own service to prevent blocking the
 main web application in case the third part service is timing-out or just
 being slow.
diff --git a/doc/runs_here.rst b/doc/runs_here.rst
index 0944e99..7efb9f1 100644
--- a/doc/runs_here.rst
+++ b/doc/runs_here.rst
@@ -7,14 +7,18 @@ List of locations where Pagure is currently used
 Please add yourself here if you run a local version of Pagure. Please also
 describe what you are using it for.
 
-+---------+------------------------------------------+--------------------------------+----------------------------------------------+
-| #       | Project                                  | Site Name                      | Used for                                     |
-+=========+==========================================+================================+==============================================+
-| 0       | `Fedora <https://fedoraproject.org>`__   | https://src.fedoraproject.org  | Development of Fedora Linux distribution     |
-+---------+------------------------------------------+--------------------------------+----------------------------------------------+
-| 1       | `CentOS <https://centos.org>`__          | https://git.centos.org         | Development of CentOS Linux distribution     |
-+---------+------------------------------------------+--------------------------------+----------------------------------------------+
-| 2       | `Midipix <https://midipix.org>`__        | https://dev.midipix.org        | Development of Midipix and related projects  |
-+---------+------------------------------------------+--------------------------------+----------------------------------------------+
-| 3       | `Cool Bug <https://coolbug.org>`__       | https://repo.coolbug.org       | Development of personal projects             |
-+---------+------------------------------------------+--------------------------------+----------------------------------------------+
++---------+---------------------------------------------------+--------------------------------+----------------------------------------------+
+| #       | Project                                           | Site Name                      | Used for                                     |
++=========+===================================================+================================+==============================================+
+| 0       | `Fedora <https://fedoraproject.org>`__            | https://src.fedoraproject.org  | Development of Fedora Linux distribution     |
++---------+---------------------------------------------------+--------------------------------+----------------------------------------------+
+| 1       | `CentOS <https://centos.org>`__                   | https://git.centos.org         | Development of CentOS Linux distribution     |
++---------+---------------------------------------------------+--------------------------------+----------------------------------------------+
+| 2       | `Midipix <https://midipix.org>`__                 | https://dev.midipix.org        | Development of Midipix and related projects  |
++---------+---------------------------------------------------+--------------------------------+----------------------------------------------+
+| 3       | `Cool Bug <https://coolbug.org>`__                | https://repo.coolbug.org       | Development of personal projects             |
++---------+---------------------------------------------------+--------------------------------+----------------------------------------------+
+| 4       | `Sergio Durigan Junior <https://sergiodj.net>`__  | https://git.sergiodj.net       | Development of personal projects             |
++---------+---------------------------------------------------+--------------------------------+----------------------------------------------+
+| 5       | `openSUSE <https://opensuse.org>`__               | https://code.opensuse.org      | Development of openSUSE community projects   |
++---------+---------------------------------------------------+--------------------------------+----------------------------------------------+
diff --git a/doc/usage/board.rst b/doc/usage/board.rst
new file mode 100644
index 0000000..396072e
--- /dev/null
+++ b/doc/usage/board.rst
@@ -0,0 +1,44 @@
+Using Boards
+============
+
+Pagure provides basic `kanban board <https://en.wikipedia.org/wiki/Kanban_(development)>`_ functionality.
+This allows the state of issues to be represented visually.
+The feature requires a specific, admin-defined tag to appear on a board.
+A repository may contain multiple boards, each with a different tag.
+
+
+Creating a Board
+----------------
+
+#. From the ``Settings`` tab, select ``Boards``
+#. Click the ``Add a new board`` button
+#. Enter a descriptive name in the ``Board name`` text box
+#. Select the tag to use in the ``Tag`` drop down
+#. Ensure the ``Active`` checkbox is checked
+#. Click the ``Update`` button to create the board
+
+After the board is created, add the status columns.
+
+#. While still on the ``Boards`` settings, click the wrench icon button
+#. If you want to use the default statuses (``Backlog``, ``Triaged``, ``In Progress``, ``In Review``, ``Done``, ``Blocked``), click the ``Populate with defaults`` button.
+#. If you wish to add non-default statuses, click the ``Add new status`` button
+    #. Enter a name for the status in the ``Status name`` text box
+    #. If you want this status to be the default for issues added to the board, select the ``Default`` radio button.
+    #. If you want this status to close the issue, check the ``Close`` check box
+    #. Select the ``Color`` for the status on the board. This is for visual distinctness; you do not have to change it.
+    #. Repeat until all of the desired statuses are added
+#. Click and drag the arrows to reorder the statuses, if desired.
+#. Click the ``Update`` button when finished.
+
+Using Boards
+------------
+
+To add an issue to a board, add the board's label to the issue.
+Alternatively, you can add an existing issue to the board by clicking the plus sign on the desired status column and adding the issue number.
+
+To change the status of an issue. go to the ``Boards`` tab and drag the card on the board into the desired status column.
+The status appears on the issue under the ``Boards`` information, but it cannot be changed from the issue.
+
+If you drag an issue to a column that has the ``Close`` boolean set, Pagure will automatically close the issue.
+
+.. note:: If you close an issue directly, Pagure will remove the board's label.
diff --git a/doc/usage/index.rst b/doc/usage/index.rst
index 1b31ec9..693fef4 100644
--- a/doc/usage/index.rst
+++ b/doc/usage/index.rst
@@ -59,6 +59,7 @@ Contents:
    upgrade_db
    pagure_ci
    quick_replies
+   board
    troubleshooting
    tips_tricks
 
diff --git a/doc/usage/pagure_ci_jenkins.rst b/doc/usage/pagure_ci_jenkins.rst
index bacfc23..14072a0 100644
--- a/doc/usage/pagure_ci_jenkins.rst
+++ b/doc/usage/pagure_ci_jenkins.rst
@@ -121,3 +121,32 @@ Example Script
     fi
 
     # Part of the script specific to how you run the tests on your project
+
+* To use the URL to POST results you need to add CI token to Jenkins instance.
+  This token could be found in Pagure project `Settings` -> `Hooks` -> `Pagure CI`.
+  In Jenkins add it in `Manage Jenkins` -> `Manage Credentials` -> `Stores scoped to Jenkins`
+  -> `Jenkins` -> `Global credentials (unrestricted)` -> `Add Credentials` as kind
+  `Secret text` (ID will be used in script).
+
+Example function used in Jenkins pipeline script
+
+::
+
+   # 'pagure-auth' is the ID of the credentials
+
+   def notifyPagurePR(repo, msg, status, phase, credentials = 'pagure-auth'){
+       def json = JsonOutput.toJson([name: 'pagure', url: env.JOB_NAME, build: [full_url: currentBuild.absoluteUrl, status: status, number: currentBuild.number, phase: phase]])
+       println json
+
+       withCredentials([string(credentialsId: credentials, variable: "PAGURE_PUSH_SECRET")]) {
+           /* We need to notify pagure that jenkins finished but then pagure will
+             wait for jenkins to be done, so if we wait for pagure's answer we're
+             basically stuck in a loop where both jenkins and pagure are waiting
+             for each other */
+           sh "timeout 1 curl -X POST -d \'$json\' https://pagure.io/api/0/ci/jenkins/$repo/\${PAGURE_PUSH_SECRET}/build-finished -H \"Content-Type: application/json\" | true"
+       }
+   }
+
+* To be able to trigger builds from Pagure CI you need to change the Global Security. Go
+  to `Manage Jenkins` -> `Configure Global Security` and find `Authorization` section.
+  In `Matrix-based security` add Read permission to `Anonymous Users` for Overall/Job/View.
diff --git a/doc/usage/project_settings.rst b/doc/usage/project_settings.rst
index 637e68e..8773c98 100644
--- a/doc/usage/project_settings.rst
+++ b/doc/usage/project_settings.rst
@@ -30,6 +30,13 @@ If the `Always merge` option is on, then the `fast-forward` option
 above is disabled in favor of the `merge` option.
 
 
+`Boards`
+--------------------------
+
+The boards feature provides simple kanban board functionality by showing issues in columns that represent state.
+The settings page lists existing boards and allows adminisrators to add new boards.
+
+
 `Comment editing`
 --------------------------
 
diff --git a/doc/usage/using_webhooks.rst b/doc/usage/using_webhooks.rst
index c3c7a7e..51b4282 100644
--- a/doc/usage/using_webhooks.rst
+++ b/doc/usage/using_webhooks.rst
@@ -17,7 +17,7 @@ Pagure will send a notification to this/these URL(s) for every action made
 on this project: new issue, new pull-request, new comments, new commits...
 
 .. note:: The notifications sent via web-hooks have the same payload as the
-    notifications sent via `fedmsg <http://www.fedmsg.com/en/latest/>`_.
+    notifications sent via `fedmsg <https://fedmsg.readthedocs.io/en/latest/>`_.
     Therefore, the list of pagure topics as well as example messages can be
     found in the `fedmsg documentation about pagure
     <https://fedora-fedmsg.readthedocs.io/en/latest/topics.html#id550>`_
diff --git a/files/mirror_project_in.py b/files/mirror_project_in.py
index 7a6a306..1b9f836 100644
--- a/files/mirror_project_in.py
+++ b/files/mirror_project_in.py
@@ -3,9 +3,6 @@
 from __future__ import print_function, absolute_import
 import os
 import argparse
-from datetime import datetime, timedelta
-
-from sqlalchemy.exc import SQLAlchemyError
 
 import pagure.config
 import pagure.lib.model as model
@@ -22,13 +19,13 @@ if "PAGURE_CONFIG" not in os.environ and os.path.exists(
 _config = pagure.config.reload_config()
 
 
-def main(check=False, debug=False):
-    """ The function pulls in all the changes from upstream"""
+def main(debug=False):
+    """The function pulls in all the changes from upstream"""
 
     session = pagure.lib.model_base.create_session(_config["DB_URL"])
     projects = (
         session.query(model.Project)
-        .filter(model.Project.mirrored_from != None)
+        .filter(model.Project.mirrored_from is not None)
         .all()
     )
 
@@ -47,14 +44,7 @@ def main(check=False, debug=False):
 
 if __name__ == "__main__":
     parser = argparse.ArgumentParser(
-        description="Script to send email before the api token expires"
-    )
-    parser.add_argument(
-        "--check",
-        dest="check",
-        action="store_true",
-        default=False,
-        help="Print the some output but does not send any email",
+        description="Script to PULL external repos into local (mirroring)"
     )
     parser.add_argument(
         "--debug",
diff --git a/files/pagure.cfg.sample b/files/pagure.cfg.sample
index bb39100..5789953 100644
--- a/files/pagure.cfg.sample
+++ b/files/pagure.cfg.sample
@@ -20,7 +20,7 @@ SECRET_KEY='<The web application secret key>'
 
 ### url to the database server:
 #DB_URL = 'mysql://user:pass@host/db_name'
-#DB_URL = 'postgres://user:pass@host/db_name'
+#DB_URL = 'postgresql://user:pass@host/db_name'
 DB_URL = 'sqlite:////var/tmp/pagure_dev.sqlite'
 
 ### Send FedMsg notifications of events in pagure
@@ -142,7 +142,7 @@ ITEM_PER_PAGE = 50
 MAX_CONTENT_LENGTH = 4 * 1024 * 1024  # 4 megabytes
 
 ### Lenght for short commits ids or file hex
-SHORT_LENGTH = 6
+SHORT_LENGTH = 7
 
 ### List of blacklisted project names that can conflicts for pagure's URLs
 ### or other
diff --git a/files/pagure.spec b/files/pagure.spec
index d08bed9..d11614a 100644
--- a/files/pagure.spec
+++ b/files/pagure.spec
@@ -1,22 +1,15 @@
 %{?python_enable_dependency_generator}
 
-%if 0%{?rhel} && 0%{?rhel} < 8
-# Since the Python 3 stack in EPEL is missing too many dependencies,
-# we're sticking with Python 2 there for now.
-%global __python %{__python2}
-%global python_pkgversion %{nil}
-%else
 # Default to Python 3 when not EL
 %global __python %{__python3}
 %global python_pkgversion %{python3_pkgversion}
-%endif
 
 # For now, to keep behavior consistent
 %global _python_bytecompile_extra 1
 
 
 Name:               pagure
-Version:            5.11.3
+Version:            5.13.2
 Release:            1%{?dist}
 Summary:            A git-centered forge
 
@@ -31,16 +24,12 @@ BuildRequires:      systemd
 BuildRequires:      python%{python_pkgversion}-devel
 BuildRequires:      python%{python_pkgversion}-setuptools
 
-%if 0%{?rhel} && 0%{?rhel} < 8
 # Required only for the `fas` and `openid` authentication backends
-Requires:           python%{python_pkgversion}-fedora-flask
+Recommends:         python%{python_pkgversion}-fedora-flask
 # Required only for the `oidc` authentication backend
-# flask-oidc
+Recommends:         python%{python_pkgversion}-flask-oidc
 # Required only if `USE_FLASK_SESSION_EXT` is set to `True`
 # flask-session
-%else
-Recommends:         python%{python_pkgversion}-fedora-flask
-%endif
 
 # We require OpenSSH 7.4+ for SHA256 support
 Requires:           openssh >= 7.4
@@ -56,12 +45,11 @@ Requires:           python%{python_pkgversion}-celery
 Requires:           python%{python_pkgversion}-chardet
 Requires:           python%{python_pkgversion}-cryptography
 Requires:           python%{python_pkgversion}-docutils
-%if ! (0%{?rhel} && 0%{?rhel} < 8)
 Requires:           python%{python_pkgversion}-email-validator
-%endif
-Requires:           python%{python_pkgversion}-enum34
 Requires:           python%{python_pkgversion}-flask
 Requires:           python%{python_pkgversion}-flask-wtf
+Requires:           python%{python_pkgversion}-flask-oidc
+Requires:           python%{python_pkgversion}-kitchen
 Requires:           python%{python_pkgversion}-markdown
 Requires:           python%{python_pkgversion}-munch
 Requires:           python%{python_pkgversion}-pillow
@@ -74,7 +62,7 @@ Requires:           python%{python_pkgversion}-redis
 Requires:           python%{python_pkgversion}-requests
 Requires:           python%{python_pkgversion}-six
 Requires:           python%{python_pkgversion}-sqlalchemy >= 0.8
-Requires:           python%{python_pkgversion}-straight-plugin
+Requires:           python%{python_pkgversion}-straight-plugin >= 1.5.0
 Requires:           python%{python_pkgversion}-whitenoise
 Requires:           python%{python_pkgversion}-wtforms
 %endif
@@ -96,9 +84,6 @@ create/merge pull-requests across or within projects.
 Summary:            Apache HTTPD configuration for Pagure
 BuildArch:          noarch
 Requires:           %{name} = %{version}-%{release}
-%if 0%{?rhel} && 0%{?rhel} < 8
-Requires:           mod_wsgi
-%else
 Requires:           httpd-filesystem
 Requires:           python%{python_pkgversion}-mod_wsgi
 %endif
@@ -228,16 +213,6 @@ of this pagure instance.
 %prep
 %autosetup -p1
 
-%if 0%{?rhel} && 0%{?rhel} < 8
-# Fix requirements.txt for EL7 setuptools
-## Remove environment markers, as they're not supported
-sed -e "s/;python_version.*$//g" -i requirements.txt
-## Drop email-validator requirement
-sed -e "s/^email_validator.*//g" -i requirements.txt
-## Drop python3-openid requirement
-sed -e "s/^python3-openid$//g" -i requirements.txt
-%endif
-
 
 %build
 %py_build
@@ -366,13 +341,11 @@ sed -e "s|#!/usr/bin/env python|#!%{__python}|" -i \
 # Switch interpreter for systemd units
 sed -e "s|/usr/bin/python|%{__python}|g" -i $RPM_BUILD_ROOT/%{_unitdir}/*.service
 
-%if ! (0%{?rhel} && 0%{?rhel} < 8)
 # Switch all systemd units to use the correct celery
 sed -e "s|/usr/bin/celery|/usr/bin/celery-3|g" -i $RPM_BUILD_ROOT/%{_unitdir}/*.service
 
 # Switch all systemd units to use the correct gunicorn
 sed -e "s|/usr/bin/gunicorn|/usr/bin/gunicorn-3|g" -i $RPM_BUILD_ROOT/%{_unitdir}/*.service
-%endif
 
 # Make log directories
 mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/log/pagure
@@ -391,12 +364,10 @@ for runnerhook in $runnerhooks; do
    ln -sf hookrunner $RPM_BUILD_ROOT/%{python_sitelib}/pagure/hooks/files/$runnerhook
 done
 
-%if 0%{?fedora} || 0%{?rhel} >= 8
 # Byte compile everything not in sitelib
 %py_byte_compile %{__python} %{buildroot}%{_datadir}/pagure/
 %py_byte_compile %{__python} %{buildroot}%{_libexecdir}/pagure/
 %py_byte_compile %{__python} %{buildroot}%{_libexecdir}/pagure-ev/
-%endif
 
 %post
 %systemd_post pagure_worker.service
@@ -476,9 +447,7 @@ done
 %dir %{_sysconfdir}/pagure/
 %dir %{_datadir}/pagure/
 %{_datadir}/pagure/*.py*
-%if ! (0%{?rhel} && 0%{?rhel} < 8)
 %{_datadir}/pagure/__pycache__/
-%endif
 %{_datadir}/pagure/alembic/
 %{_libexecdir}/pagure/
 %{python_sitelib}/pagure/
@@ -567,6 +536,21 @@ done
 
 
 %changelog
+* Wed Feb 10 2021 Pierre-Yves Chibon <pingou@pingoured.fr> - 5.13.2-1
+- Update to 5.13.2
+
+* Fri Jan 29 2021 Pierre-Yves Chibon <pingou@pingoured.fr> - 5.13.1-1
+- Update to 5.13.1
+
+* Tue Jan 19 2021 Pierre-Yves Chibon <pingou@pingoured.fr> - 5.13-1
+- Update to 5.13
+
+* Fri Jan 08 2021 Pierre-Yves Chibon <pingou@pingoured.fr> - 5.12.1-1
+- Update to 5.12.1
+
+* Wed Jan 06 2021 Pierre-Yves Chibon <pingou@pingoured.fr> - 5.12-1
+- Update to 5.12
+
 * Tue Aug 11 2020 Pierre-Yves Chibon <pingou@pingoured.fr> - 5.11.3-1
 - Update to 5.11.3
 
diff --git a/files/pagure_authorized_keys_worker.service b/files/pagure_authorized_keys_worker.service
index 2ba126b..ad3eb6e 100644
--- a/files/pagure_authorized_keys_worker.service
+++ b/files/pagure_authorized_keys_worker.service
@@ -4,7 +4,7 @@ After=redis.target
 Documentation=https://pagure.io/pagure
 
 [Service]
-ExecStart=/usr/bin/celery worker -A pagure.lib.tasks --loglevel=info -c 1 -Q authorized_keys_queue
+ExecStart=/usr/bin/celery -A pagure.lib.tasks worker --loglevel=INFO -c 1 -Q authorized_keys_queue -n authorized_keys
 Environment="PAGURE_CONFIG=/etc/pagure/pagure.cfg"
 Type=simple
 User=git
diff --git a/files/pagure_ci.service b/files/pagure_ci.service
index 853c474..a00dc51 100644
--- a/files/pagure_ci.service
+++ b/files/pagure_ci.service
@@ -8,7 +8,7 @@ After=redis.target
 Documentation=https://pagure.io/pagure
 
 [Service]
-ExecStart=/usr/bin/celery worker -A pagure.lib.tasks_services --loglevel=info -Q pagure_ci
+ExecStart=/usr/bin/celery -A pagure.lib.tasks_services worker --loglevel=INFO -Q pagure_ci
 Environment="PAGURE_CONFIG=/etc/pagure/pagure.cfg"
 Type=simple
 User=git
diff --git a/files/pagure_gitolite_worker.service b/files/pagure_gitolite_worker.service
index d92d1c6..5f9bb02 100644
--- a/files/pagure_gitolite_worker.service
+++ b/files/pagure_gitolite_worker.service
@@ -4,7 +4,7 @@ After=redis.target
 Documentation=https://pagure.io/pagure
 
 [Service]
-ExecStart=/usr/bin/celery worker -A pagure.lib.tasks --loglevel=info -c 1 -Q gitolite_queue
+ExecStart=/usr/bin/celery -A pagure.lib.tasks worker --loglevel=INFO -c 1 -Q gitolite_queue -n gitolite
 Environment="PAGURE_CONFIG=/etc/pagure/pagure.cfg"
 Type=simple
 User=git
diff --git a/files/pagure_loadjson.service b/files/pagure_loadjson.service
index 414d318..8f56888 100644
--- a/files/pagure_loadjson.service
+++ b/files/pagure_loadjson.service
@@ -8,7 +8,7 @@ After=redis.target
 Documentation=https://pagure.io/pagure
 
 [Service]
-ExecStart=/usr/bin/celery worker -A pagure.lib.tasks_services --loglevel=info -Q pagure_loadjson
+ExecStart=/usr/bin/celery -A pagure.lib.tasks_services worker --loglevel=INFO -Q pagure_loadjson -n load_json
 Environment="PAGURE_CONFIG=/etc/pagure/pagure.cfg"
 Type=simple
 User=git
diff --git a/files/pagure_logcom.service b/files/pagure_logcom.service
index 360bf9d..b31bf02 100644
--- a/files/pagure_logcom.service
+++ b/files/pagure_logcom.service
@@ -8,7 +8,7 @@ After=redis.target
 Documentation=https://pagure.io/pagure
 
 [Service]
-ExecStart=/usr/bin/celery worker -A pagure.lib.tasks_services --loglevel=info -Q pagure_logcom
+ExecStart=/usr/bin/celery -A pagure.lib.tasks_services worker --loglevel=INFO -Q pagure_logcom -n logcom
 Environment="PAGURE_CONFIG=/etc/pagure/pagure.cfg"
 Type=simple
 User=git
diff --git a/files/pagure_mirror.service b/files/pagure_mirror.service
index 0605db8..b0700c2 100644
--- a/files/pagure_mirror.service
+++ b/files/pagure_mirror.service
@@ -13,7 +13,7 @@ After=redis.target
 Documentation=https://pagure.io/pagure
 
 [Service]
-ExecStart=/usr/bin/celery worker -A pagure.lib.tasks_mirror --loglevel=info -Q pagure_mirror
+ExecStart=/usr/bin/celery -A pagure.lib.tasks_mirror worker --loglevel=INFO -Q pagure_mirror
 Environment="PAGURE_CONFIG=/etc/pagure/pagure.cfg"
 Type=simple
 User=mirror
diff --git a/files/pagure_webhook.service b/files/pagure_webhook.service
index fc5fc1c..0bd57af 100644
--- a/files/pagure_webhook.service
+++ b/files/pagure_webhook.service
@@ -8,7 +8,7 @@ After=redis.target
 Documentation=https://pagure.io/pagure
 
 [Service]
-ExecStart=/usr/bin/celery worker -A pagure.lib.tasks_services --loglevel=info -Q pagure_webhook
+ExecStart=/usr/bin/celery -A pagure.lib.tasks_services worker --loglevel=INFO -Q pagure_webhook -n webhook
 Environment="PAGURE_CONFIG=/etc/pagure/pagure.cfg"
 Type=simple
 User=git
diff --git a/files/pagure_worker.service b/files/pagure_worker.service
index 6cb46f5..dd67826 100644
--- a/files/pagure_worker.service
+++ b/files/pagure_worker.service
@@ -4,7 +4,7 @@ After=redis.target
 Documentation=https://pagure.io/pagure
 
 [Service]
-ExecStart=/usr/bin/celery worker -A pagure.lib.tasks --loglevel=info
+ExecStart=/usr/bin/celery -A pagure.lib.tasks worker --loglevel=INFO -n worker
 Environment="PAGURE_CONFIG=/etc/pagure/pagure.cfg"
 Type=simple
 User=git
diff --git a/files/pagure_worker.service.example b/files/pagure_worker.service.example
index c8f172a..0195f5e 100644
--- a/files/pagure_worker.service.example
+++ b/files/pagure_worker.service.example
@@ -10,7 +10,7 @@ After=redis.target
 Documentation=https://pagure.io/pagure
 
 [Service]
-ExecStart=/usr/bin/celery worker -A pagure.lib.tasks --loglevel=info -Q <queue_name>
+ExecStart=/usr/bin/celery -A pagure.lib.tasks worker --loglevel=INFO -Q <queue_name>
 Environment="PAGURE_CONFIG=/etc/pagure/pagure.cfg"
 Type=simple
 User=git
diff --git a/pagure-ev/pagure_stream_server.py b/pagure-ev/pagure_stream_server.py
index d1aeb00..6908ea7 100644
--- a/pagure-ev/pagure_stream_server.py
+++ b/pagure-ev/pagure_stream_server.py
@@ -170,7 +170,7 @@ def handle_client(client_reader, client_writer):
     try:
         obj = get_obj_from_path(url.path)
     except PagureException as err:
-        log.warning(err.message)
+        log.warning(str(err))
         return
 
     origin = pagure.config.config.get("APP_URL")
@@ -253,7 +253,6 @@ def main():
             handle_client,
             host=None,
             port=pagure.config.config["EVENTSOURCE_PORT"],
-            loop=loop,
         )
         SERVER = loop.run_until_complete(coro)
         log.info(
@@ -264,7 +263,6 @@ def main():
                 stats,
                 host=None,
                 port=pagure.config.config.get("EV_STATS_PORT"),
-                loop=loop,
             )
             stats_server = loop.run_until_complete(stats_coro)
             log.info(
diff --git a/pagure-milters/comment_email_milter.py b/pagure-milters/comment_email_milter.py
index 3627d70..0cf1d28 100644
--- a/pagure-milters/comment_email_milter.py
+++ b/pagure-milters/comment_email_milter.py
@@ -149,7 +149,7 @@ class PagureMilter(Milter.Base):
             self.log(
                 "No valid recipient email found in To/Cc: %s" % email_address
             )
-            return Milter.CONTINUE
+            return Milter.ACCEPT
 
         if msg["From"] and msg["From"] == _config.get("FROM_EMAIL"):
             self.log("Let's not process the email we send")
@@ -158,6 +158,11 @@ class PagureMilter(Milter.Base):
         msg_id = msg.get("In-Reply-To", None)
         if msg_id is None:
             self.log("No In-Reply-To, can't process this message.")
+            self.setreply(
+                "554",
+                xcode="5.5.0",
+                msg="Replies to Pagure must have an In-Reply-To header field."
+            )
             return Milter.REJECT
 
         # Ensure we don't get extra lines in the message-id
@@ -203,22 +208,58 @@ class PagureMilter(Milter.Base):
             self.log("tohash:    %s" % tohash)
             self.log("Hash does not correspond to the destination")
             session.remove()
+            self.setreply(
+                "550", xcode="5.7.1", msg="Reply authentication failed."
+            )
             return Milter.REJECT
 
         msg_id = clean_item(msg_id)
 
-        if msg_id and "-ticket-" in msg_id:
-            self.log("Processing issue")
-            session.remove()
-            return self.handle_ticket_email(msg, msg_id)
-        elif msg_id and "-pull-request-" in msg_id:
-            self.log("Processing pull-request")
-            session.remove()
-            return self.handle_request_email(msg, msg_id)
-        else:
-            self.log("Not a pagure ticket or pull-request email, let it go")
-            session.remove()
-            return Milter.CONTINUE
+        try:
+            if msg_id and "-ticket-" in msg_id:
+                self.log("Processing issue")
+                session.remove()
+                return self.handle_ticket_email(msg, msg_id)
+            elif msg_id and "-pull-request-" in msg_id:
+                self.log("Processing pull-request")
+                session.remove()
+                return self.handle_request_email(msg, msg_id)
+            else:
+                # msg_id passed the hash check, and yet wasn't recognized as
+                # a message ID generated by Pagure. This is probably a bug,
+                # because it should be impossible unless an attacker has
+                # acquired the secret "salt" or broken the hash algorithm.
+                self.log(
+                    "Not a pagure ticket or pull-request email, rejecting it."
+                )
+                session.remove()
+                self.setreply(
+                    "554",
+                    xcode="5.3.5",
+                    msg="Pagure couldn't determine how to handle the message."
+                )
+                return Milter.REJECT
+        except requests.ReadTimeout as e:
+            self.setreply(
+                "451",
+                xcode="4.4.2",
+                msg="The comment couldn't be added: " + str(e)
+            )
+            return Milter.TEMPFAIL
+        except requests.ConnectionError as e:
+            self.setreply(
+                "451",
+                xcode="4.4.1",
+                msg="The comment couldn't be added: " + str(e)
+            )
+            return Milter.TEMPFAIL
+        except requests.RequestException as e:
+            self.setreply(
+                "554",
+                xcode="5.3.0",
+                msg="The comment couldn't be added: " + str(e)
+            )
+            return Milter.REJECT
 
     def handle_ticket_email(self, emailobj, msg_id):
         """ Add the email as a comment on a ticket. """
@@ -245,10 +286,23 @@ class PagureMilter(Milter.Base):
         req = requests.put(url, data=data)
         if req.status_code == 200:
             self.log("Comment added")
-            return Milter.ACCEPT
+            # The message is now effectively delivered. Tell the MTA to accept
+            # and discard it.
+            # If you want the message to be processed by another milter after
+            # DISCARD one, or delivered to a mailbox the usual way, then change
+            # DROP to ACCEPT.
+            return Milter.DISCARD
         self.log("Could not add the comment to ticket to pagure")
         self.log(req.text)
 
+        self.setreply(
+            "554",
+            xcode="5.3.0",
+            msg=(
+                "The comment couldn't be added to the issue. "
+                + "HTTP status: %d %s." % (req.status_code, req.reason)
+            )
+        )
         return Milter.REJECT
 
     def handle_request_email(self, emailobj, msg_id):
@@ -276,10 +330,23 @@ class PagureMilter(Milter.Base):
         req = requests.put(url, data=data)
         if req.status_code == 200:
             self.log("Comment added on PR")
-            return Milter.ACCEPT
+            # The message is now effectively delivered. Tell the MTA to accept
+            # and discard it.
+            # If you want the message to be processed by another milter after
+            # this one, or delivered to a mailbox the usual way, then change
+            # DISCARD to ACCEPT.
+            return Milter.DISCARD
         self.log("Could not add the comment to PR to pagure")
         self.log(req.text)
 
+        self.setreply(
+            "554",
+            xcode="5.3.0",
+            msg=(
+                "The comment couldn't be added to the pull request. "
+                + "HTTP status: %d %s." % (req.status_code, req.reason)
+            )
+        )
         return Milter.REJECT
 
 
diff --git a/pagure.egg-info/PKG-INFO b/pagure.egg-info/PKG-INFO
index 5d1234d..8905704 100644
--- a/pagure.egg-info/PKG-INFO
+++ b/pagure.egg-info/PKG-INFO
@@ -1,25 +1,24 @@
-Metadata-Version: 1.2
+Metadata-Version: 2.1
 Name: pagure
-Version: 5.11.3
+Version: 5.13.2
 Summary: A light-weight git-centered forge based on pygit2.
 Home-page: https://pagure.io/pagure/
+Download-URL: https://pagure.io/releases/pagure/
 Author: Pierre-Yves Chibon
 Author-email: pingou@pingoured.fr
 Maintainer: Pierre-Yves Chibon
 Maintainer-email: pingou@pingoured.fr
 License: GPLv2+
-Download-URL: https://pagure.io/releases/pagure/
-Description: UNKNOWN
-Platform: UNKNOWN
 Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)
 Classifier: Operating System :: POSIX :: Linux
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
 Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
 Classifier: Topic :: Software Development :: Bug Tracking
 Classifier: Topic :: Software Development :: Version Control
+License-File: LICENSE
diff --git a/pagure.egg-info/SOURCES.txt b/pagure.egg-info/SOURCES.txt
index 82e0f32..5c6842d 100644
--- a/pagure.egg-info/SOURCES.txt
+++ b/pagure.egg-info/SOURCES.txt
@@ -5,7 +5,6 @@ UPGRADING.rst
 createdb.py
 pyproject.toml
 requirements.txt
-setup.cfg
 setup.py
 alembic/env.py
 alembic/script.py.mako
@@ -85,6 +84,7 @@ alembic/versions/a13967424130_add_pr_tags_table.py
 alembic/versions/abc71fd60fa_add_the_closed_by_column_to_pull_.py
 alembic/versions/b5efae6bb23_add_merge_status_to_the_pull_requests_.py
 alembic/versions/ba538b2648b7_create_hook_mirror_table.py
+alembic/versions/c0bffa4e8fbc_token_if_in_commit_flag_can_be_null.py
 alembic/versions/c34f4b09ef18_star_a_project.py
 alembic/versions/d4d2c5aa8a0_add_granularity_to_watching_repos.py
 alembic/versions/d7589827abbb_add_support_for_allow_rebase.py
@@ -118,178 +118,6 @@ doc/overview_simple.ascii
 doc/plugins.rst
 doc/requirements.txt
 doc/runs_here.rst
-doc/_build/doctrees/changelog.doctree
-doc/_build/doctrees/configuration.doctree
-doc/_build/doctrees/contributing.doctree
-doc/_build/doctrees/contributors.doctree
-doc/_build/doctrees/custom_gitolite_conf.doctree
-doc/_build/doctrees/development.doctree
-doc/_build/doctrees/environment.pickle
-doc/_build/doctrees/index.doctree
-doc/_build/doctrees/install.doctree
-doc/_build/doctrees/install_crons.doctree
-doc/_build/doctrees/install_pagure_ci.doctree
-doc/_build/doctrees/install_pagure_ev.doctree
-doc/_build/doctrees/install_pagure_loadjson.doctree
-doc/_build/doctrees/install_pagure_logcom.doctree
-doc/_build/doctrees/install_pagure_milter.doctree
-doc/_build/doctrees/install_pagure_webhooks.doctree
-doc/_build/doctrees/overview.doctree
-doc/_build/doctrees/plugins.doctree
-doc/_build/doctrees/usage/first_steps.doctree
-doc/_build/doctrees/usage/flags.doctree
-doc/_build/doctrees/usage/forks.doctree
-doc/_build/doctrees/usage/http_push.doctree
-doc/_build/doctrees/usage/index.doctree
-doc/_build/doctrees/usage/magic_words.doctree
-doc/_build/doctrees/usage/markdown.doctree
-doc/_build/doctrees/usage/pagure_ci.doctree
-doc/_build/doctrees/usage/pagure_ci_jenkins.doctree
-doc/_build/doctrees/usage/pr_custom_page.doctree
-doc/_build/doctrees/usage/project_acls.doctree
-doc/_build/doctrees/usage/project_settings.doctree
-doc/_build/doctrees/usage/pull_requests.doctree
-doc/_build/doctrees/usage/quick_replies.doctree
-doc/_build/doctrees/usage/read_only.doctree
-doc/_build/doctrees/usage/roadmap.doctree
-doc/_build/doctrees/usage/theming.doctree
-doc/_build/doctrees/usage/ticket_templates.doctree
-doc/_build/doctrees/usage/tips_tricks.doctree
-doc/_build/doctrees/usage/troubleshooting.doctree
-doc/_build/doctrees/usage/upgrade_db.doctree
-doc/_build/doctrees/usage/using_doc.doctree
-doc/_build/doctrees/usage/using_webhooks.doctree
-doc/_build/doctrees/usage/trouble/inaccessible_pr.doctree
-doc/_build/html/.buildinfo
-doc/_build/html/changelog.html
-doc/_build/html/configuration.html
-doc/_build/html/contributing.html
-doc/_build/html/contributors.html
-doc/_build/html/custom_gitolite_conf.html
-doc/_build/html/development.html
-doc/_build/html/genindex.html
-doc/_build/html/index.html
-doc/_build/html/install.html
-doc/_build/html/install_crons.html
-doc/_build/html/install_pagure_ci.html
-doc/_build/html/install_pagure_ev.html
-doc/_build/html/install_pagure_loadjson.html
-doc/_build/html/install_pagure_logcom.html
-doc/_build/html/install_pagure_milter.html
-doc/_build/html/install_pagure_webhooks.html
-doc/_build/html/objects.inv
-doc/_build/html/overview.html
-doc/_build/html/plugins.html
-doc/_build/html/search.html
-doc/_build/html/searchindex.js
-doc/_build/html/_images/inaccessible_pr.png
-doc/_build/html/_images/overview.png
-doc/_build/html/_images/overview_simple.png
-doc/_build/html/_images/pagure_add_ssh_key.png
-doc/_build/html/_images/pagure_commit_flag.png
-doc/_build/html/_images/pagure_custom_pr.png
-doc/_build/html/_images/pagure_flag_pr.png
-doc/_build/html/_images/pagure_my_settings.png
-doc/_build/html/_images/pagure_pr_commits.png
-doc/_build/html/_images/pagure_pr_overview.png
-doc/_build/html/_images/pagure_pr_pull_requests.png
-doc/_build/html/_images/pagure_roadmap2.png
-doc/_build/html/_images/pagure_ticket_template.png
-doc/_build/html/_images/quick_replies.png
-doc/_build/html/_sources/changelog.rst.txt
-doc/_build/html/_sources/configuration.rst.txt
-doc/_build/html/_sources/contributing.rst.txt
-doc/_build/html/_sources/contributors.rst.txt
-doc/_build/html/_sources/custom_gitolite_conf.rst.txt
-doc/_build/html/_sources/development.rst.txt
-doc/_build/html/_sources/index.rst.txt
-doc/_build/html/_sources/install.rst.txt
-doc/_build/html/_sources/install_crons.rst.txt
-doc/_build/html/_sources/install_pagure_ci.rst.txt
-doc/_build/html/_sources/install_pagure_ev.rst.txt
-doc/_build/html/_sources/install_pagure_loadjson.rst.txt
-doc/_build/html/_sources/install_pagure_logcom.rst.txt
-doc/_build/html/_sources/install_pagure_milter.rst.txt
-doc/_build/html/_sources/install_pagure_webhooks.rst.txt
-doc/_build/html/_sources/overview.rst.txt
-doc/_build/html/_sources/plugins.rst.txt
-doc/_build/html/_sources/usage/first_steps.rst.txt
-doc/_build/html/_sources/usage/flags.rst.txt
-doc/_build/html/_sources/usage/forks.rst.txt
-doc/_build/html/_sources/usage/http_push.rst.txt
-doc/_build/html/_sources/usage/index.rst.txt
-doc/_build/html/_sources/usage/magic_words.rst.txt
-doc/_build/html/_sources/usage/markdown.rst.txt
-doc/_build/html/_sources/usage/pagure_ci.rst.txt
-doc/_build/html/_sources/usage/pagure_ci_jenkins.rst.txt
-doc/_build/html/_sources/usage/pr_custom_page.rst.txt
-doc/_build/html/_sources/usage/project_acls.rst.txt
-doc/_build/html/_sources/usage/project_settings.rst.txt
-doc/_build/html/_sources/usage/pull_requests.rst.txt
-doc/_build/html/_sources/usage/quick_replies.rst.txt
-doc/_build/html/_sources/usage/read_only.rst.txt
-doc/_build/html/_sources/usage/roadmap.rst.txt
-doc/_build/html/_sources/usage/theming.rst.txt
-doc/_build/html/_sources/usage/ticket_templates.rst.txt
-doc/_build/html/_sources/usage/tips_tricks.rst.txt
-doc/_build/html/_sources/usage/troubleshooting.rst.txt
-doc/_build/html/_sources/usage/upgrade_db.rst.txt
-doc/_build/html/_sources/usage/using_doc.rst.txt
-doc/_build/html/_sources/usage/using_webhooks.rst.txt
-doc/_build/html/_sources/usage/trouble/inaccessible_pr.rst.txt
-doc/_build/html/_static/basic.css
-doc/_build/html/_static/cloud.css
-doc/_build/html/_static/cloud.js
-doc/_build/html/_static/doctools.js
-doc/_build/html/_static/documentation_options.js
-doc/_build/html/_static/file.png
-doc/_build/html/_static/fonts.css
-doc/_build/html/_static/icon-caution.png
-doc/_build/html/_static/icon-danger.png
-doc/_build/html/_static/icon-deprecated.png
-doc/_build/html/_static/icon-note.png
-doc/_build/html/_static/icon-seealso.png
-doc/_build/html/_static/icon-todo.png
-doc/_build/html/_static/icon-warning.png
-doc/_build/html/_static/jquery-3.2.1.js
-doc/_build/html/_static/jquery.cookie.js
-doc/_build/html/_static/jquery.js
-doc/_build/html/_static/language_data.js
-doc/_build/html/_static/minus.png
-doc/_build/html/_static/overview.png
-doc/_build/html/_static/overview_simple.png
-doc/_build/html/_static/pagure.png
-doc/_build/html/_static/plus.png
-doc/_build/html/_static/pygments.css
-doc/_build/html/_static/quick_replies.png
-doc/_build/html/_static/searchtools.js
-doc/_build/html/_static/site.css
-doc/_build/html/_static/underscore-1.3.1.js
-doc/_build/html/_static/underscore.js
-doc/_build/html/usage/first_steps.html
-doc/_build/html/usage/flags.html
-doc/_build/html/usage/forks.html
-doc/_build/html/usage/http_push.html
-doc/_build/html/usage/index.html
-doc/_build/html/usage/magic_words.html
-doc/_build/html/usage/markdown.html
-doc/_build/html/usage/pagure_ci.html
-doc/_build/html/usage/pagure_ci_jenkins.html
-doc/_build/html/usage/pr_custom_page.html
-doc/_build/html/usage/project_acls.html
-doc/_build/html/usage/project_settings.html
-doc/_build/html/usage/pull_requests.html
-doc/_build/html/usage/quick_replies.html
-doc/_build/html/usage/read_only.html
-doc/_build/html/usage/roadmap.html
-doc/_build/html/usage/theming.html
-doc/_build/html/usage/ticket_templates.html
-doc/_build/html/usage/tips_tricks.html
-doc/_build/html/usage/troubleshooting.html
-doc/_build/html/usage/upgrade_db.html
-doc/_build/html/usage/using_doc.html
-doc/_build/html/usage/using_webhooks.html
-doc/_build/html/usage/trouble/inaccessible_pr.html
 doc/_static/fonts.css
 doc/_static/overview.png
 doc/_static/overview_simple.png
@@ -297,6 +125,7 @@ doc/_static/pagure.png
 doc/_static/quick_replies.png
 doc/_static/site.css
 doc/_templates/pagure-logo.html
+doc/usage/board.rst
 doc/usage/first_steps.rst
 doc/usage/flags.rst
 doc/usage/forks.rst
@@ -616,6 +445,7 @@ pagure/templates/settings_api_keys.html
 pagure/templates/settings_block_users.html
 pagure/templates/settings_board.html
 pagure/templates/settings_boards.html
+pagure/templates/settings_git_branches.html
 pagure/templates/settings_milestones.html
 pagure/templates/settings_mirrorlog.html
 pagure/templates/settings_options.html
@@ -867,6 +697,7 @@ pagure/themes/srcfpo/static/icons/bugzilla.png
 pagure/themes/srcfpo/static/icons/community.png
 pagure/themes/srcfpo/static/icons/koji.png
 pagure/themes/srcfpo/static/icons/koschei.png
+pagure/themes/srcfpo/static/icons/transtats.png
 pagure/themes/srcfpo/templates/group_info.html
 pagure/themes/srcfpo/templates/repo_branches.html
 pagure/themes/srcfpo/templates/repo_info.html
@@ -888,7 +719,6 @@ pagure/ui/oidc_login.py
 pagure/ui/plugins.py
 pagure/ui/repo.py
 tests/__init__.py
-tests/broker
 tests/pagure.png
 tests/placebo.png
 tests/test_alembic.py
@@ -918,8 +748,11 @@ tests/test_pagure_flask_api_plugins_view_project.py
 tests/test_pagure_flask_api_pr_flag.py
 tests/test_pagure_flask_api_project.py
 tests/test_pagure_flask_api_project_blockuser.py
+tests/test_pagure_flask_api_project_contributors.py
 tests/test_pagure_flask_api_project_delete_project.py
+tests/test_pagure_flask_api_project_git_alias.py
 tests/test_pagure_flask_api_project_git_tags.py
+tests/test_pagure_flask_api_project_hascommit.py
 tests/test_pagure_flask_api_project_tags.py
 tests/test_pagure_flask_api_project_update_watch.py
 tests/test_pagure_flask_api_project_view_file.py
@@ -949,6 +782,7 @@ tests/test_pagure_flask_ui_issues_read_only.py
 tests/test_pagure_flask_ui_issues_templates.py
 tests/test_pagure_flask_ui_login.py
 tests/test_pagure_flask_ui_no_master_branch.py
+tests/test_pagure_flask_ui_oidc_login.py
 tests/test_pagure_flask_ui_old_commit.py
 tests/test_pagure_flask_ui_plugins.py
 tests/test_pagure_flask_ui_plugins_default_hook.py
@@ -964,6 +798,7 @@ tests/test_pagure_flask_ui_plugins_pagure_request_hook.py
 tests/test_pagure_flask_ui_plugins_pagure_ticket_hook.py
 tests/test_pagure_flask_ui_plugins_rtd_hook.py
 tests/test_pagure_flask_ui_plugins_unsigned.py
+tests/test_pagure_flask_ui_pr_bidi.py
 tests/test_pagure_flask_ui_pr_edit.py
 tests/test_pagure_flask_ui_pr_no_sources.py
 tests/test_pagure_flask_ui_priorities.py
diff --git a/pagure.egg-info/entry_points.txt b/pagure.egg-info/entry_points.txt
index b9d778c..be5a380 100644
--- a/pagure.egg-info/entry_points.txt
+++ b/pagure.egg-info/entry_points.txt
@@ -1,11 +1,9 @@
+[console_scripts]
+pagure-admin = pagure.cli.admin:main
 
-    [console_scripts]
-    pagure-admin=pagure.cli.admin:main
-
-    [pagure.git_auth.helpers]
-    test_auth = pagure.lib.git_auth:GitAuthTestHelper
-    gitolite2 = pagure.lib.git_auth:Gitolite2Auth
-    gitolite3 = pagure.lib.git_auth:Gitolite3Auth
-    pagure = pagure.lib.git_auth:PagureGitAuth
-    pagure_authorized_keys = pagure.lib.git_auth:PagureGitAuth
-    
\ No newline at end of file
+[pagure.git_auth.helpers]
+gitolite2 = pagure.lib.git_auth:Gitolite2Auth
+gitolite3 = pagure.lib.git_auth:Gitolite3Auth
+pagure = pagure.lib.git_auth:PagureGitAuth
+pagure_authorized_keys = pagure.lib.git_auth:PagureGitAuth
+test_auth = pagure.lib.git_auth:GitAuthTestHelper
diff --git a/pagure.egg-info/requires.txt b/pagure.egg-info/requires.txt
index 74331a1..d965e70 100644
--- a/pagure.egg-info/requires.txt
+++ b/pagure.egg-info/requires.txt
@@ -1,3 +1,4 @@
+Pillow
 alembic
 arrow
 bcrypt
@@ -8,30 +9,22 @@ celery
 chardet
 cryptography
 docutils
+email_validator
 flask
 flask-wtf
 kitchen
 markdown
 munch
-Pillow
 psutil
 pygit2>=0.26.0
 python-openid-cla
 python-openid-teams
+python3-openid
 redis
 requests
+setuptools
 six
 sqlalchemy>=0.8
-straight.plugin
+straight.plugin>=1.5.0
 whitenoise
 wtforms
-
-[:python_version < "3.4"]
-enum34
-
-[:python_version <= "2.7"]
-python-openid
-
-[:python_version >= "3.0"]
-email_validator
-python3-openid
diff --git a/pagure/__init__.py b/pagure/__init__.py
index 5526abc..823f4ae 100644
--- a/pagure/__init__.py
+++ b/pagure/__init__.py
@@ -8,8 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
-
-__api_version__ = "0.30"
-__version__ = "5.11.3"
+__api_version__ = "0.31"
+__version__ = "5.13.2"
diff --git a/pagure/api/__init__.py b/pagure/api/__init__.py
index ae5485d..f6e51b8 100644
--- a/pagure/api/__init__.py
+++ b/pagure/api/__init__.py
@@ -14,15 +14,15 @@ API namespace version 0.
 # pylint: disable=too-few-public-methods
 # pylint: disable=too-many-locals
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import codecs
+import enum
 import functools
 import logging
 import os
 
 import docutils
-import enum
 import flask
 import markupsafe
 from six.moves.urllib_parse import urljoin
@@ -33,20 +33,19 @@ API = flask.Blueprint("api_ns", __name__, url_prefix="/api/0")
 import pagure.lib.query  # noqa: E402
 import pagure.lib.tasks  # noqa: E402
 from pagure.config import config as pagure_config  # noqa: E402
-from pagure.doc_utils import (  # noqa: E402
+from pagure.doc_utils import (  # noqa: E402, I202
     load_doc_title_and_name,
-    modify_rst,
     modify_html,
-)  # noqa: E402
+    modify_rst,
+)
 from pagure.exceptions import APIError  # noqa: E402
 from pagure.utils import authenticated, check_api_acls  # noqa: E402
 
-
 _log = logging.getLogger(__name__)
 
 
 def preload_docs(endpoint):
-    """ Utility to load an RST file and turn it into fancy HTML. """
+    """Utility to load an RST file and turn it into fancy HTML."""
 
     here = os.path.dirname(os.path.abspath(__file__))
     fname = os.path.join(here, "..", "doc", endpoint + ".rst")
@@ -64,7 +63,7 @@ APIDOC = preload_docs("api")
 
 
 def build_docs_section(name, endpoints):
-    """ Utility to build a documentation section to feed the template. """
+    """Utility to build a documentation section to feed the template."""
 
     result = {"name": name, "endpoints": []}
 
@@ -75,8 +74,7 @@ def build_docs_section(name, endpoints):
 
 
 class APIERROR(enum.Enum):
-    """ Clast listing as Enum all the possible error thrown by the API.
-    """
+    """Clast listing as Enum all the possible error thrown by the API."""
 
     ENOCODE = "Variable message describing the issue"
     ENOPROJECT = "Project not found"
@@ -152,7 +150,7 @@ class APIERROR(enum.Enum):
 
 
 def get_authorized_api_project(session, repo, user=None, namespace=None):
-    """ Helper function to get an authorized_project with optional lock. """
+    """Helper function to get an authorized_project with optional lock."""
     repo = pagure.lib.query.get_authorized_project(
         flask.g.session, repo, user=user, namespace=namespace
     )
@@ -161,11 +159,17 @@ def get_authorized_api_project(session, repo, user=None, namespace=None):
 
 
 def get_request_data():
-    return flask.request.form or flask.request.get_json() or {}
+    # Check if it's JSON or form data
+    if flask.request.headers.get("Content-Type") == "application/json":
+        # Set force to True to ignore the mimetype. Set silent so that None is
+        # returned if it's invalid JSON.
+        return flask.request.get_json(force=True, silent=True) or {}
+    else:
+        return flask.request.form or flask.request.get_json() or {}
 
 
 def api_login_required(acls=None, optional=False):
-    """ Decorator used to indicate that authentication is required for some
+    """Decorator used to indicate that authentication is required for some
     API endpoint.
 
     :arg acls: A list of access control
@@ -173,11 +177,11 @@ def api_login_required(acls=None, optional=False):
     """
 
     def decorator(function):
-        """ The decorator of the function """
+        """The decorator of the function"""
 
         @functools.wraps(function)
         def decorated_function(*args, **kwargs):
-            """ Actually does the job with the arguments provided. """
+            """Actually does the job with the arguments provided."""
 
             response = check_api_acls(acls, optional)
             if response:
@@ -221,16 +225,16 @@ def api_login_required(acls=None, optional=False):
 
 
 def api_login_optional(acls=None):
-    """ Decorator used to indicate that authentication is optional for some
+    """Decorator used to indicate that authentication is optional for some
     API endpoint.
     """
 
     def decorator(function):
-        """ The decorator of the function """
+        """The decorator of the function"""
 
         @functools.wraps(function)
         def decorated_function(*args, **kwargs):
-            """ Actually does the job with the arguments provided. """
+            """Actually does the job with the arguments provided."""
 
             response = check_api_acls(acls, optional=True)
             if response:
@@ -243,11 +247,11 @@ def api_login_optional(acls=None):
 
 
 def api_method(function):
-    """ Runs an API endpoint and catch all the APIException thrown. """
+    """Runs an API endpoint and catch all the APIException thrown."""
 
     @functools.wraps(function)
     def wrapper(*args, **kwargs):
-        """ Actually does the job with the arguments provided. """
+        """Actually does the job with the arguments provided."""
         try:
             result = function(*args, **kwargs)
         except APIError as err:
@@ -278,7 +282,7 @@ def api_method(function):
 
 
 def get_page():
-    """ Returns the page value specified in the request.
+    """Returns the page value specified in the request.
     Defaults to 1.
     raises APIERROR.EINVALIDREQ if the page provided isn't an integer
     raises APIERROR.EINVALIDREQ if the page provided is lower than 1
@@ -304,7 +308,7 @@ def get_page():
 
 
 def get_per_page():
-    """ Returns the per_page value specified in the request.
+    """Returns the per_page value specified in the request.
     Defaults to 20.
     raises APIERROR.EINVALIDREQ if the page provided isn't an integer
     raises APIERROR.EINVALIDPERPAGEVALUE if the page provided is lower
@@ -330,11 +334,12 @@ def get_per_page():
 if pagure_config.get("ENABLE_TICKETS", True):
     from pagure.api import issue  # noqa: E402
     from pagure.api import boards  # noqa: E402, F401
-from pagure.api import fork  # noqa: E402
-from pagure.api import project  # noqa: E402
-from pagure.api import user  # noqa: E402
-from pagure.api import group  # noqa: E402
-from pagure.api import plugins  # noqa: E402
+
+from pagure.api import fork  # noqa: E402, I202
+from pagure.api import group  # noqa: E402, I202
+from pagure.api import plugins  # noqa: E402, I202
+from pagure.api import project  # noqa: E402, I202
+from pagure.api import user  # noqa: E402, I202
 
 if pagure_config.get("PAGURE_CI_SERVICES", False):
     from pagure.api.ci import jenkins  # noqa: E402, F401
@@ -513,7 +518,7 @@ def api_error_codes():
 
 @API.route("/")
 def api():
-    """ Display the api information page. """
+    """Display the api information page."""
 
     sections = []
 
@@ -528,9 +533,14 @@ def api():
         project.api_project_tag_delete,
         project.api_git_tags,
         project.api_new_git_tags,
+        project.api_git_branches,
+        project.api_list_git_alias,
+        project.api_new_git_alias,
+        project.api_drop_git_alias,
         project.api_project_git_urls,
         project.api_project_watchers,
-        project.api_git_branches,
+        project.api_project_contributors,
+        project.api_project_hascommit,
         project.api_new_branch,
         project.api_fork_project,
         project.api_modify_acls,
@@ -579,6 +589,7 @@ def api():
         fork.api_pull_request_merge,
         fork.api_pull_request_rebase,
         fork.api_pull_request_close,
+        fork.api_pull_request_reopen,
         fork.api_pull_request_add_comment,
         fork.api_pull_request_add_flag,
         fork.api_pull_request_get_flag,
diff --git a/pagure/api/boards.py b/pagure/api/boards.py
index e42f7cb..d78aee2 100644
--- a/pagure/api/boards.py
+++ b/pagure/api/boards.py
@@ -8,32 +8,27 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import logging
 
 import flask
-from sqlalchemy.exc import SQLAlchemyError
 import werkzeug.datastructures
+from sqlalchemy.exc import SQLAlchemyError
 
 import pagure
 import pagure.exceptions
 import pagure.lib.query
 import pagure.lib.tasks
-from pagure.forms import TAGS_REGEX, TAGS_REGEX_RE
 from pagure.api import (
     API,
-    api_method,
-    api_login_required,
     APIERROR,
+    api_login_required,
+    api_method,
     get_request_data,
 )
-from pagure.api.utils import (
-    _get_repo,
-    _check_token,
-    _check_issue_tracker,
-)
-
+from pagure.api.utils import _check_issue_tracker, _check_token, _get_repo
+from pagure.forms import TAGS_REGEX, TAGS_REGEX_RE
 
 _log = logging.getLogger(__name__)
 
@@ -91,7 +86,8 @@ def api_boards_view(repo, username=None, namespace=None):
 @API.route("/<namespace>/<repo>/boards", methods=["POST"])
 @API.route("/fork/<username>/<repo>/boards", methods=["POST"])
 @API.route(
-    "/fork/<username>/<namespace>/<repo>/boards", methods=["POST"],
+    "/fork/<username>/<namespace>/<repo>/boards",
+    methods=["POST"],
 )
 @api_login_required(acls=["modify_project"])
 @api_method
@@ -228,7 +224,9 @@ def api_board_create(repo, username=None, namespace=None):
     if removing_names:
         try:
             pagure.lib.query.delete_board(
-                flask.g.session, project=repo, names=removing_names,
+                flask.g.session,
+                project=repo,
+                names=removing_names,
             )
             flask.g.session.commit()
         except SQLAlchemyError as err:  # pragma: no cover
@@ -245,7 +243,8 @@ def api_board_create(repo, username=None, namespace=None):
 @API.route("/<namespace>/<repo>/boards/delete", methods=["POST"])
 @API.route("/fork/<username>/<repo>/boards/delete", methods=["POST"])
 @API.route(
-    "/fork/<username>/<namespace>/<repo>/boards/delete", methods=["POST"],
+    "/fork/<username>/<namespace>/<repo>/boards/delete",
+    methods=["POST"],
 )
 @api_login_required(acls=["modify_project"])
 @api_method
@@ -311,7 +310,9 @@ def api_board_delete(repo, username=None, namespace=None):
 
     try:
         pagure.lib.query.delete_board(
-            flask.g.session, project=repo, names=names,
+            flask.g.session,
+            project=repo,
+            names=names,
         )
         flask.g.session.commit()
     except SQLAlchemyError as err:  # pragma: no cover
@@ -451,7 +452,9 @@ def api_board_status(repo, board_name, username=None, namespace=None):
 
     if board is None:
         raise pagure.exceptions.APIError(
-            404, error_code=APIERROR.EINVALIDREQ, errors="Board not found",
+            404,
+            error_code=APIERROR.EINVALIDREQ,
+            errors="Board not found",
         )
 
     data = flask.request.get_json() or {}
@@ -510,7 +513,9 @@ def api_board_status(repo, board_name, username=None, namespace=None):
 
         try:
             close_status = data[name].get("close_status") or None
-            close = data[name].get("close") or True if close_status else False
+            close = data[name].get("close") or (
+                True if close_status else False
+            )
             if close_status not in repo.close_status:
                 close_status = None
 
@@ -609,7 +614,9 @@ def api_board_ticket_update_status(
 
     if board is None:
         raise pagure.exceptions.APIError(
-            404, error_code=APIERROR.EINVALIDREQ, errors="Board not found",
+            404,
+            error_code=APIERROR.EINVALIDREQ,
+            errors="Board not found",
         )
 
     data = flask.request.get_json() or {}
@@ -665,7 +672,8 @@ def api_board_ticket_update_status(
     "/<namespace>/<repo>/boards/<board_name>/add_issue", methods=["POST"]
 )
 @API.route(
-    "/fork/<username>/<repo>/boards/<board_name>/add_issue", methods=["POST"],
+    "/fork/<username>/<repo>/boards/<board_name>/add_issue",
+    methods=["POST"],
 )
 @API.route(
     "/fork/<username>/<namespace>/<repo>/boards/<board_name>/add_issue",
@@ -735,7 +743,9 @@ def api_board_ticket_add_status(
 
     if board is None:
         raise pagure.exceptions.APIError(
-            404, error_code=APIERROR.EINVALIDREQ, errors="Board not found",
+            404,
+            error_code=APIERROR.EINVALIDREQ,
+            errors="Board not found",
         )
 
     data = flask.request.get_json() or {}
diff --git a/pagure/api/ci/jenkins.py b/pagure/api/ci/jenkins.py
index cbaf910..7c23dc6 100644
--- a/pagure/api/ci/jenkins.py
+++ b/pagure/api/ci/jenkins.py
@@ -8,23 +8,21 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import logging
 
 import flask
-
 from cryptography.hazmat.primitives import constant_time
 from kitchen.text.converters import to_bytes
 
 import pagure
 import pagure.exceptions
-import pagure.lib.query
-import pagure.lib.plugins
 import pagure.lib.lib_ci as lib_ci
+import pagure.lib.plugins
+import pagure.lib.query
 from pagure.api import API, APIERROR, api_method
 
-
 _log = logging.getLogger(__name__)
 
 
diff --git a/pagure/api/fork.py b/pagure/api/fork.py
index 4a389d3..bcfaae2 100644
--- a/pagure/api/fork.py
+++ b/pagure/api/fork.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import logging
 
@@ -22,25 +22,24 @@ import pagure.lib.query
 import pagure.lib.tasks
 from pagure.api import (
     API,
-    api_method,
-    api_login_required,
     APIERROR,
+    api_login_required,
+    api_method,
     get_authorized_api_project,
-    get_request_data,
     get_page,
     get_per_page,
+    get_request_data,
 )
-from pagure.config import config as pagure_config
-from pagure.utils import is_repo_committer, is_true
 from pagure.api.utils import (
-    _get_repo,
-    _check_token,
-    _get_request,
+    _check_private_pull_request_access,
     _check_pull_request,
     _check_pull_request_access,
-    _check_private_pull_request_access,
+    _check_token,
+    _get_repo,
+    _get_request,
 )
-
+from pagure.config import config as pagure_config
+from pagure.utils import is_repo_committer, is_true
 
 _log = logging.getLogger(__name__)
 
@@ -512,7 +511,10 @@ def api_pull_request_update(repo, requestid, username=None, namespace=None):
         )
     else:
         request.title = form.title.data.strip()
-        request.initial_comment = form.initial_comment.data.strip()
+        request.initial_comment = ""
+        # This value is optional, check first if it's filled
+        if form.initial_comment.data:
+            request.initial_comment = form.initial_comment.data.strip()
         flask.g.session.add(request)
         if not request.private and not request.project.private:
             pagure.lib.notify.log(
@@ -770,10 +772,13 @@ def api_pull_request_close(repo, requestid, username=None, namespace=None):
 
     repo = _get_repo(repo, username, namespace)
     _check_pull_request(repo)
-    _check_token(repo)
+    _check_token(repo, project_token=False)
     request = _get_request(repo, requestid)
 
-    if not is_repo_committer(repo):
+    if (
+        not is_repo_committer(repo)
+        and not flask.g.fas_user.username == request.user.username
+    ):
         raise pagure.exceptions.APIError(403, error_code=APIERROR.ENOPRCLOSE)
 
     try:
@@ -791,6 +796,74 @@ def api_pull_request_close(repo, requestid, username=None, namespace=None):
     return jsonout
 
 
+@API.route("/<repo>/pull-request/<int:requestid>/reopen", methods=["POST"])
+@API.route(
+    "/<namespace>/<repo>/pull-request/<int:requestid>/reopen", methods=["POST"]
+)
+@API.route(
+    "/fork/<username>/<repo>/pull-request/<int:requestid>/reopen",
+    methods=["POST"],
+)
+@API.route(
+    "/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/reopen",
+    methods=["POST"],
+)
+@api_login_required(acls=["pull_request_close", "pull_request_update"])
+@api_method
+def api_pull_request_reopen(repo, requestid, username=None, namespace=None):
+    """
+    Reopen a pull-request
+    --------------------
+    Instruct Pagure to reopen a pull request.
+
+    ::
+
+        POST /api/0/<repo>/pull-request/<request id>/reopen
+        POST /api/0/<namespace>/<repo>/pull-request/<request id>/reopen
+
+    ::
+
+        POST /api/0/fork/<username>/<repo>/pull-request/<request id>/reopen
+        POST /api/0/fork/<username>/<namespace>/<repo>/pull-request/<request id>/reopen
+
+    Sample response
+    ^^^^^^^^^^^^^^^
+
+    ::
+
+        {
+          "message": "Pull-request reopened!"
+        }
+
+    """  # noqa
+    output = {}
+
+    repo = _get_repo(repo, username, namespace)
+    _check_pull_request(repo)
+    _check_token(repo, project_token=False)
+    request = _get_request(repo, requestid)
+
+    if (
+        not is_repo_committer(repo)
+        and not flask.g.fas_user.username == request.user.username
+    ):
+        raise pagure.exceptions.APIError(403, error_code=APIERROR.ENOPRCLOSE)
+
+    try:
+        pagure.lib.query.reopen_pull_request(
+            flask.g.session, request, flask.g.fas_user.username
+        )
+        flask.g.session.commit()
+        output["message"] = "Pull-request reopened!"
+    except SQLAlchemyError as err:  # pragma: no cover
+        flask.g.session.rollback()
+        _log.exception(err)
+        raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
+
+    jsonout = flask.jsonify(output)
+    return jsonout
+
+
 @API.route("/<repo>/pull-request/<int:requestid>/comment", methods=["POST"])
 @API.route(
     "/<namespace>/<repo>/pull-request/<int:requestid>/comment",
@@ -928,6 +1001,13 @@ def api_pull_request_add_flag(repo, requestid, username=None, namespace=None):
     -------------------
     Add or edit flags on a pull-request.
 
+    This is an utility method which add a flag to the latest commit of the
+    specified pull-request.
+
+    Note that using it has a risk of race-condition if the pull-request changes
+    at the time the flag is being added. Using the commit flag endpoint prevents
+    this risk of race-condition.
+
     ::
 
         POST /api/0/<repo>/pull-request/<request id>/flag
@@ -1064,9 +1144,27 @@ def api_pull_request_add_flag(repo, requestid, username=None, namespace=None):
                     else pagure_config["FLAG_FAILURE"]
                 )
         try:
+            if not request.commit_stop:
+                repopath = None
+                parentpath = pagure.utils.get_repo_path(request.project)
+                if request.remote:
+                    repopath = pagure.utils.get_remote_repo_path(
+                        request.remote_git, request.branch_from
+                    )
+                elif request.project_from:
+                    repopath = pagure.utils.get_repo_path(request.project_from)
+
+                repo_obj = None
+                if repopath:
+                    repo_obj = pygit2.Repository(repopath)
+                orig_repo = pygit2.Repository(parentpath)
+                pagure.lib.git.diff_pull_request(
+                    flask.g.session, request, repo_obj, orig_repo
+                )
+
             # New Flag
             message, uid = pagure.lib.query.add_pull_request_flag(
-                flask.g.session,
+                session=flask.g.session,
                 request=request,
                 username=username,
                 status=status,
@@ -1078,8 +1176,8 @@ def api_pull_request_add_flag(repo, requestid, username=None, namespace=None):
                 token=flask.g.token.id,
             )
             flask.g.session.commit()
-            pr_flag = pagure.lib.query.get_pull_request_flag_by_uid(
-                flask.g.session, request, uid
+            pr_flag = pagure.lib.query.get_commit_flag_by_uid(
+                flask.g.session, request.commit_stop, uid
             )
             output["message"] = message
             output["uid"] = uid
@@ -1182,9 +1280,31 @@ def api_pull_request_get_flag(repo, requestid, username=None, namespace=None):
     _check_pull_request(repo)
     request = _get_request(repo, requestid)
 
+    if not request.commit_stop:
+        repopath = None
+        parentpath = pagure.utils.get_repo_path(request.project)
+        if request.remote:
+            repopath = pagure.utils.get_remote_repo_path(
+                request.remote_git, request.branch_from
+            )
+        elif request.project_from:
+            repopath = pagure.utils.get_repo_path(request.project_from)
+
+        repo_obj = None
+        if repopath:
+            repo_obj = pygit2.Repository(repopath)
+        orig_repo = pygit2.Repository(parentpath)
+        pagure.lib.git.diff_pull_request(
+            flask.g.session, request, repo_obj, orig_repo
+        )
+
     output = {"flags": []}
 
-    for flag in request.flags:
+    flags = pagure.lib.query.get_commit_flag(
+        flask.g.session, request.project, request.commit_stop
+    )
+
+    for flag in flags:
         output["flags"].append(flag.to_json(public=True))
 
     jsonout = flask.jsonify(output)
@@ -1353,6 +1473,11 @@ def api_pull_request_create(repo, username=None, namespace=None):
     |                       |          |             | changes are about.     |
     +-----------------------+----------+-------------+------------------------+
 
+    Note: If f the repo you're opening the PR against is not the same as the
+    repo from which the changes originates, you must provide the ``repo_from``
+    and ``repo_from_username`` (and potentially ``repo_from_namespace``)
+    corresponding to the fork.
+
     Sample response
     ^^^^^^^^^^^^^^^
 
@@ -1480,7 +1605,10 @@ def api_pull_request_create(repo, username=None, namespace=None):
     if orig_commit:
         orig_commit = orig_commit.oid.hex
 
-    initial_comment = form.initial_comment.data.strip() or None
+    initial_comment = None
+    # This value is optional, check first if it's filled
+    if form.initial_comment.data:
+        initial_comment = form.initial_comment.data.strip()
 
     commit_start = commit_stop = None
     if diff_commits:
diff --git a/pagure/api/group.py b/pagure/api/group.py
index 4c58d02..664e37b 100644
--- a/pagure/api/group.py
+++ b/pagure/api/group.py
@@ -9,7 +9,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import flask
 
@@ -19,8 +19,8 @@ import pagure.lib.query
 from pagure.api import (
     API,
     APIERROR,
-    api_method,
     api_login_optional,
+    api_method,
     get_page,
     get_per_page,
 )
@@ -140,25 +140,36 @@ def api_view_group(group):
 
         GET /api/0/group/some_group_name?projects=1&acl=commit
 
+    ::
+
+        GET /api/0/group/some_group_name?page=1&per_page=50
+
     Input
     ^^^^^
 
-    +------------------+---------+--------------+-----------------------------+
-    | Key              | Type    | Optionality  | Description                 |
-    +==================+=========+==============+=============================+
-    | ``group name``   | str     | Mandatory    | The name of the group to    |
-    |                  |         |              | retrieve information about. |
-    +------------------+---------+--------------+-----------------------------+
-    | ``projects``     | bool    | Optional     | Specifies whether to include|
-    |                  |         |              | projects in the data        |
-    |                  |         |              | returned.                   |
-    +------------------+---------+--------------+-----------------------------+
-    | ``acl``          | str     | Optional     | Filter the project returned |
-    |                  |         |              | (if any) to those where the |
-    |                  |         |              | has the specified ACL level.|
-    |                  |         |              | Can be any of: ``admin``,   |
-    |                  |         |              | ``commit`` or ``ticket``.   |
-    +------------------+---------+--------------+-----------------------------+
+    +-----------------------+---------+--------------+-----------------------------+
+    | Key                   | Type    | Optionality  | Description                 |
+    +=======================+=========+==============+=============================+
+    | ``group name``        | str     | Mandatory    | The name of the group to    |
+    |                       |         |              | retrieve information about. |
+    +-----------------------+---------+--------------+-----------------------------+
+    | ``projects``          | bool    | Optional     | Specifies whether to include|
+    |                       |         |              | projects in the data        |
+    |                       |         |              | returned.                   |
+    +-----------------------+---------+--------------+-----------------------------+
+    | ``acl``               | str     | Optional     | Filter the project returned |
+    |                       |         |              | (if any) to those where the |
+    |                       |         |              | has the specified ACL level.|
+    |                       |         |              | Can be any of: ``admin``,   |
+    |                       |         |              | ``commit`` or ``ticket``.   |
+    +-----------------------+---------+--------------+-----------------------------+
+    | ``page``              | int     | Optional     | Specifies which page to     |
+    |                       |         |              | return (defaults to: 1)     |
+    +-----------------------+---------+--------------+-----------------------------+
+    | ``per_page``          | int     | Optional     | The number of projects      |
+    |                       |         |              | to return per page.         |
+    |                       |         |              | The maximum is 100.         |
+    +-----------------------+---------+--------------+-----------------------------+
 
 
     Sample response
@@ -179,6 +190,15 @@ def api_view_group(group):
           "description": "Some Group",
           "display_name": "Some Group",
           "group_type": "user",
+          "pagination": {
+            "first": "http://127.0.0.1:5000/api/0/group/some_group_name?per_page=2&page=1",
+            "last": "http://127.0.0.1:5000/api/0/group/some_group_name?per_page=2&page=2",
+            "next": "http://127.0.0.1:5000/api/0/group/some_group_name?per_page=2&page=2",
+            "page": 1,
+            "pages": 2,
+            "per_page": 2,
+            "prev": null
+          },
           "members": [
             "user1",
             "user2"
@@ -206,6 +226,19 @@ def api_view_group(group):
             "user2"
           ],
           "name": "some_group_name",
+          "total_projects": 1000,
+          "pagination": {
+            "first":
+                "http://127.0.0.1:5000/api/0/group/some_group_name?per_page=2&projects=1&page=1",
+            "last":
+                "http://127.0.0.1:5000/api/0/group/some_group_name?per_page=2&projects=1&page=500",
+            "next":
+                "http://127.0.0.1:5000/api/0/group/some_group_name?per_page=2&projects=1&page=2",
+            "page": 1,
+            "pages": 500,
+            "per_page": 2,
+            "prev": null
+          },
           "projects": [],
         }
 
@@ -228,15 +261,30 @@ def api_view_group(group):
         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOGROUP)
 
     output = group.to_json(public=(not pagure.utils.api_authenticated()))
-    if projects and not acl:
-        output["projects"] = [
-            project.to_json(public=True) for project in group.projects
-        ]
-    elif projects and acl:
+
+    if projects:
+        # Prepare pagination data for projects
+        if not acl:
+            group_projects = group.projects
+        elif acl:
+            group_projects = [
+                pg.project for pg in group.projects_groups if pg.access in acl
+            ]
+        page = get_page()
+        per_page = get_per_page()
+        projects_cnt = len(group_projects)
+        pagination_metadata = pagure.lib.query.get_pagination_metadata(
+            flask.request, page, per_page, projects_cnt
+        )
+        query_start = (page - 1) * per_page
+        query_limit = per_page
+        page_projects = group_projects[query_start : query_start + query_limit]
+
+        output["total_projects"] = projects_cnt
+        output["pagination"] = pagination_metadata
+
         output["projects"] = [
-            pg.project.to_json(public=True)
-            for pg in group.projects_groups
-            if pg.access in acl
+            project.to_json(public=True) for project in page_projects
         ]
     jsonout = flask.jsonify(output)
     jsonout.status_code = 200
diff --git a/pagure/api/issue.py b/pagure/api/issue.py
index 17987e3..23f50d8 100644
--- a/pagure/api/issue.py
+++ b/pagure/api/issue.py
@@ -8,41 +8,41 @@
 
 """
 
-from __future__ import print_function, unicode_literals, absolute_import
+from __future__ import absolute_import, print_function, unicode_literals
 
-import flask
 import datetime
 import logging
 
 import arrow
+import flask
 from sqlalchemy.exc import SQLAlchemyError
 
 import pagure.exceptions
 import pagure.lib.query
 from pagure.api import (
     API,
-    api_method,
-    api_login_required,
-    api_login_optional,
     APIERROR,
-    get_request_data,
+    api_login_optional,
+    api_login_required,
+    api_method,
     get_page,
     get_per_page,
+    get_request_data,
+)
+from pagure.api.utils import (
+    _check_issue_tracker,
+    _check_private_issue_access,
+    _check_ticket_access,
+    _check_token,
+    _get_issue,
+    _get_repo,
 )
 from pagure.config import config as pagure_config
 from pagure.utils import (
     api_authenticated,
     is_repo_committer,
-    urlpattern,
     is_true,
-)
-from pagure.api.utils import (
-    _get_repo,
-    _check_token,
-    _get_issue,
-    _check_issue_tracker,
-    _check_ticket_access,
-    _check_private_issue_access,
+    urlpattern,
 )
 
 _log = logging.getLogger(__name__)
@@ -1590,11 +1590,20 @@ def api_view_issues_history_detailed_stats(
         }
 
     """  # noqa
+    weeks_range = flask.request.args.get("weeks_range") or 53
+    try:
+        weeks_range = int(weeks_range)
+    except Exception:
+        weeks_range = 53
+
     repo = _get_repo(repo, username, namespace)
     _check_issue_tracker(repo)
 
     stats = pagure.lib.query.issues_history_stats(
-        flask.g.session, repo, detailed=True
+        flask.g.session,
+        repo,
+        detailed=True,
+        weeks_range=weeks_range,
     )
     jsonout = flask.jsonify({"stats": stats})
     return jsonout
diff --git a/pagure/api/plugins.py b/pagure/api/plugins.py
index a5fa1c5..baa588a 100644
--- a/pagure/api/plugins.py
+++ b/pagure/api/plugins.py
@@ -8,25 +8,24 @@
 
 """
 
-from __future__ import print_function, unicode_literals, absolute_import
+from __future__ import absolute_import, print_function, unicode_literals
 
-import flask
 import logging
 
+import flask
 from sqlalchemy.exc import SQLAlchemyError
 
 import pagure.exceptions
-import pagure.lib.query
-
 import pagure.lib.plugins as plugins_lib
+import pagure.lib.query
 from pagure.api import (
     API,
-    api_method,
-    api_login_required,
-    api_login_optional,
     APIERROR,
+    api_login_optional,
+    api_login_required,
+    api_method,
 )
-from pagure.api.utils import _get_repo, _check_token, _check_plugin
+from pagure.api.utils import _check_plugin, _check_token, _get_repo
 
 _log = logging.getLogger(__name__)
 
diff --git a/pagure/api/project.py b/pagure/api/project.py
index 9a0a1db..1ac8c68 100644
--- a/pagure/api/project.py
+++ b/pagure/api/project.py
@@ -8,15 +8,14 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
-import flask
 import logging
 
+import flask
 import pygit2
-
-from sqlalchemy.exc import SQLAlchemyError
 from six import string_types
+from sqlalchemy.exc import SQLAlchemyError
 
 try:
     from pygit2 import AlreadyExistsError
@@ -25,26 +24,25 @@ except ImportError:
     AlreadyExistsError = ValueError
 
 import pagure
-import pagure.forms
 import pagure.exceptions
+import pagure.forms
 import pagure.lib.git
 import pagure.lib.query
 import pagure.utils
 from pagure.api import (
     API,
-    api_method,
     APIERROR,
+    api_login_optional,
     api_login_required,
+    api_method,
     get_authorized_api_project,
-    api_login_optional,
-    get_request_data,
     get_page,
     get_per_page,
+    get_request_data,
 )
-from pagure.api.utils import _get_repo, _check_token, _get_project_tag
+from pagure.api.utils import _check_token, _get_project_tag, _get_repo
 from pagure.config import config as pagure_config
 
-
 _log = logging.getLogger(__name__)
 
 
@@ -1627,15 +1625,8 @@ def api_modify_project(repo, namespace=None):
         )
 
     valid_keys = ["main_admin", "retain_access"]
-    # Check if it's JSON or form data
-    if flask.request.headers.get("Content-Type") == "application/json":
-        # Set force to True to ignore the mimetype. Set silent so that None is
-        # returned if it's invalid JSON.
-        args = flask.request.get_json(force=True, silent=True) or {}
-        retain_access = args.get("retain_access", False)
-    else:
-        args = get_request_data()
-        retain_access = args.get("retain_access", "").lower() in ["true", "1"]
+    args = get_request_data()
+    retain_access = args.get("retain_access", "").lower() in ["true", "1"]
 
     if not args:
         raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ)
@@ -1752,7 +1743,9 @@ def api_fork_project():
     if form.validate_on_submit():
         repo = form.repo.data
         username = form.username.data or None
-        namespace = form.namespace.data.strip() or None
+        namespace = None
+        if form.namespace.data:
+            namespace = form.namespace.data.strip()
 
         repo = get_authorized_api_project(
             flask.g.session, repo, user=username, namespace=namespace
@@ -1847,14 +1840,7 @@ def api_generate_acls(repo, username=None, namespace=None):
     project = _get_repo(repo, username, namespace)
     _check_token(project, project_token=False)
 
-    # Check if it's JSON or form data
-    if flask.request.headers.get("Content-Type") == "application/json":
-        # Set force to True to ignore the mimetype. Set silent so that None is
-        # returned if it's invalid JSON.
-        json = flask.request.get_json(force=True, silent=True) or {}
-        wait = json.get("wait", False)
-    else:
-        wait = pagure.utils.is_true(get_request_data().get("wait"))
+    wait = pagure.utils.is_true(get_request_data().get("wait"))
 
     try:
         task = pagure.lib.git.generate_gitolite_acls(project=project)
@@ -1926,13 +1912,7 @@ def api_new_branch(repo, username=None, namespace=None):
     project = _get_repo(repo, username, namespace)
     _check_token(project, project_token=False)
 
-    # Check if it's JSON or form data
-    if flask.request.headers.get("Content-Type") == "application/json":
-        # Set force to True to ignore the mimetype. Set silent so that None is
-        # returned if it's invalid JSON.
-        args = flask.request.get_json(force=True, silent=True) or {}
-    else:
-        args = get_request_data()
+    args = get_request_data()
 
     branch = args.get("branch")
     from_branch = args.get("from_branch")
@@ -1969,6 +1949,229 @@ def api_new_branch(repo, username=None, namespace=None):
     return jsonout
 
 
+@API.route("/<repo>/git/alias/drop", methods=["POST"])
+@API.route("/<namespace>/<repo>/git/alias/drop", methods=["POST"])
+@API.route("/fork/<username>/<repo>/git/alias/drop", methods=["POST"])
+@API.route(
+    "/fork/<username>/<namespace>/<repo>/git/alias/drop", methods=["POST"]
+)
+@api_login_required(
+    acls=["delete_git_alias", "modify_git_alias", "modify_project"]
+)
+@api_method
+def api_drop_git_alias(repo, username=None, namespace=None):
+    """
+    Delete a git branch alias
+    -------------------------
+    Delete an existing git branch alias from a project.
+
+    ::
+
+        POST /api/0/rpms/python-requests/git/alias/drop
+
+
+    Input
+    ^^^^^
+
+    +------------------+---------+--------------+----------------------------+
+    | Key              | Type    | Optionality  | Description                |
+    +==================+=========+==============+============================+
+    | ``alias_from``   | string  | Mandatory    | | The origin reference the |
+    |                  |         |              |   alias is for.            |
+    +------------------+---------+--------------+----------------------------+
+    | ``alias_to``     | string  | Mandatory    | | The destination reference|
+    |                  |         |              |   of the alias (must be an |
+    |                  |         |              |   existing branch in the   |
+    |                  |         |              |   git repository).         |
+    +------------------+---------+--------------+----------------------------+
+
+    Note: while the references are listed as ``refs/heads/...`` the alias_from
+        and alias_to need to be specified as the basic branch name that they
+        are (ie: ``refs/heads/main`` needs to be specified as ``main``).
+
+
+    Sample input
+    ^^^^^^^^^^^^
+
+    ::
+
+        {
+          'alias_from': 'main',
+          'alias_to': 'rawhide'
+        }
+
+
+
+    Sample response
+    ^^^^^^^^^^^^^^^
+
+    ::
+
+        {
+            "refs/heads/rawhide": "refs/heads/main"
+        }
+
+    """
+    project = _get_repo(repo, username, namespace)
+    _check_token(project, project_token=False)
+
+    args = get_request_data()
+
+    alias_from = args.get("alias_from")
+    alias_to = args.get("alias_to")
+
+    if (
+        not alias_from
+        or (alias_from and not isinstance(alias_from, string_types))
+    ) or (
+        not alias_to or (alias_to and not isinstance(alias_to, string_types))
+    ):
+        raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ)
+
+    try:
+        pagure.lib.git.drop_branch_aliases(project, alias_from, alias_to)
+    except KeyError:
+        raise pagure.exceptions.APIError(
+            400, error_code=APIERROR.EBRANCHNOTFOUND
+        )
+
+    return api_list_git_alias(repo, username, namespace)
+
+
+@API.route("/<repo>/git/alias/new", methods=["POST"])
+@API.route("/<namespace>/<repo>/git/alias/new", methods=["POST"])
+@API.route("/fork/<username>/<repo>/git/alias/new", methods=["POST"])
+@API.route(
+    "/fork/<username>/<namespace>/<repo>/git/alias/new", methods=["POST"]
+)
+@api_login_required(
+    acls=["create_git_alias", "modify_git_alias", "modify_project"]
+)
+@api_method
+def api_new_git_alias(repo, username=None, namespace=None):
+    """
+    Create a git branch alias
+    -------------------------
+    Create a new git branch alias in a project.
+
+    ::
+
+        POST /api/0/rpms/python-requests/git/alias/new
+
+
+    Input
+    ^^^^^
+
+    +------------------+---------+--------------+----------------------------+
+    | Key              | Type    | Optionality  | Description                |
+    +==================+=========+==============+============================+
+    | ``alias_from``   | string  | Mandatory    | | The origin reference the |
+    |                  |         |              |   alias is for.            |
+    +------------------+---------+--------------+----------------------------+
+    | ``alias_to``     | string  | Mandatory    | | The destination reference|
+    |                  |         |              |   of the alias (must be an |
+    |                  |         |              |   existing branch in the   |
+    |                  |         |              |   git repository).         |
+    +------------------+---------+--------------+----------------------------+
+
+    Note: while the references are listed as ``refs/heads/...`` the alias_from
+        and alias_to need to be specified as the basic branch name that they
+        are (ie: ``refs/heads/main`` needs to be specified as ``main``).
+
+
+    Sample input
+    ^^^^^^^^^^^^
+
+    ::
+
+        {
+          'alias_from': 'main',
+          'alias_to': 'rawhide'
+        }
+
+
+    Sample response
+    ^^^^^^^^^^^^^^^
+
+    ::
+
+        {
+            "refs/heads/rawhide": "refs/heads/main"
+        }
+
+    """
+    project = _get_repo(repo, username, namespace)
+    _check_token(project, project_token=False)
+
+    args = get_request_data()
+
+    alias_from = args.get("alias_from")
+    alias_to = args.get("alias_to")
+
+    if (
+        not alias_from
+        or (alias_from and not isinstance(alias_from, string_types))
+    ) or (
+        not alias_to or (alias_to and not isinstance(alias_to, string_types))
+    ):
+        raise pagure.exceptions.APIError(
+            400,
+            error_code=APIERROR.EINVALIDREQ,
+            error="Invalid input for alias_from or alias_to",
+        )
+
+    try:
+        pagure.lib.git.set_branch_alias(project, alias_from, alias_to)
+    except KeyError:
+        raise pagure.exceptions.APIError(
+            400, error_code=APIERROR.EBRANCHNOTFOUND
+        )
+    except pagure.exceptions.PagureException as error:
+        raise pagure.exceptions.APIError(
+            400, error_code=APIERROR.ENOCODE, error=str(error)
+        )
+
+    return api_list_git_alias(repo, username, namespace)
+
+
+@API.route("/<repo>/git/alias")
+@API.route("/<namespace>/<repo>/git/alias")
+@API.route("/fork/<username>/<repo>/git/alias")
+@API.route("/fork/<username>/<namespace>/<repo>/git/alias")
+@api_method
+def api_list_git_alias(repo, username=None, namespace=None):
+    """
+    List git branch alias
+    ---------------------
+    List the existing git branch alias in a project.
+
+    ::
+
+        GET /api/0/rpms/python-requests/git/alias
+
+
+    Sample response
+    ^^^^^^^^^^^^^^^
+
+    ::
+
+        {
+            "refs/heads/rawhide": "refs/heads/main"
+        }
+
+    """
+    project = _get_repo(repo, username, namespace)
+    _check_token(project, project_token=False)
+
+    try:
+        output = pagure.lib.git.get_branch_aliases(project)
+    except pygit2.GitError:  # pragma: no cover
+        raise pagure.exceptions.APIError(400, error_code=APIERROR.EGITERROR)
+
+    jsonout = flask.jsonify(output)
+    return jsonout
+
+
 @API.route("/<repo>/c/<commit_hash>/flag")
 @API.route("/<namespace>/<repo>/c/<commit_hash>/flag")
 @API.route("/fork/<username>/<repo>/c/<commit_hash>/flag")
@@ -2596,7 +2799,19 @@ def api_modify_acls(repo, namespace=None, username=None):
                         400, error_code=APIERROR.EINVALIDREQ, errors="%s" % err
                     )
             elif group:
-                pass
+                _log.info(
+                    "Looking at removing group %s from project %s",
+                    group,
+                    project.fullname,
+                )
+                for grp in project.groups:
+                    if grp.id == group_obj.id:
+                        project.groups.remove(grp)
+                        break
+                pagure.lib.query.update_read_only_mode(
+                    flask.g.session, project, read_only=True
+                )
+                pagure.lib.git.generate_gitolite_acls(project=project)
 
         try:
             flask.g.session.commit()
@@ -2811,12 +3026,11 @@ def api_get_project_webhook_token(repo, username=None, namespace=None):
 
 
 def _check_value(value):
-    """ Convert the provided value into a boolean, an int or leave it as it.
-    """
+    """Convert the provided value into a boolean, an int or leave it as it."""
     if str(value).lower() in ["true"]:
         value = True
     elif str(value).lower() in ["false"]:
-        value = True
+        value = False
     elif str(value).isdigit():
         value = int(value)
     return value
@@ -2853,6 +3067,19 @@ def api_modify_project_options(repo, username=None, namespace=None):
     do not specify in the request values that have been changed before they
     will go back to their default value.
 
+    The fields and values can be specified either as a regular HTTP form or as
+    a JSON blob.
+
+    Sample request body
+    ^^^^^^^^^^^^^^^^^^^
+
+    ::
+
+       {
+           'issue_tracker': false,
+           'disable_non_fast-forward_merges': true
+       }
+
     Sample response
     ^^^^^^^^^^^^^^^
 
@@ -2868,9 +3095,10 @@ def api_modify_project_options(repo, username=None, namespace=None):
     _check_token(project, project_token=False)
 
     settings = {}
-    for key in flask.request.form:
+    request_data = get_request_data()
+    for key in request_data:
 
-        settings[key] = _check_value(flask.request.form[key])
+        settings[key] = _check_value(request_data[key])
 
     try:
         message = pagure.lib.query.update_project_settings(
@@ -3073,6 +3301,115 @@ def api_project_block_user(repo, namespace=None, username=None):
     return jsonout
 
 
+@API.route("/<repo>/contributors")
+@API.route("/<namespace>/<repo>/contributors")
+@API.route("/fork/<username>/<repo>/contributors")
+@API.route("/fork/<username>/<namespace>/<repo>/contributors")
+@api_method
+def api_project_contributors(repo, namespace=None, username=None):
+    """
+    Contributors of a project
+    -------------------------
+    List all the contributors of a project, by their access level.
+
+    ::
+
+        GET /api/0/<repo>/contributors
+        GET /api/0/<namespace>/<repo>/contributors
+
+    ::
+
+        GET /api/0/fork/<username>/<repo>/contributors
+        GET /api/0/fork/<username>/<namespace>/<repo>/contributors
+
+    Sample response
+    ^^^^^^^^^^^^^^^
+
+    ::
+
+        {
+          "groups": {
+            "admin": [],
+            "collaborators": [
+              {
+                "branches": "f*",
+                "user": "packager"
+              }
+            ],
+            "commit": [
+              "infra"
+            ],
+            "ticket": []
+          },
+          "users": {
+            "admin": [
+              "pingou"
+            ],
+            "collaborators": [
+              {
+                "branches": "epel*",
+                "user": "ngompa"
+              }
+            ],
+            "commit": [
+              "kevin"
+            ],
+            "ticket": [
+              "ralph"
+            ]
+          }
+        }
+
+    """
+
+    project = _get_repo(repo, username, namespace)
+
+    # USERS
+    admins = set([u.user for u in project.admins + [project.user]])
+    committers = set(u.user for u in project.committers)
+    collaborators = set([u.user.user for u in project.collaborators])
+    users = set([u.user for u in project.users])
+
+    output_users = {
+        "admin": sorted(admins),
+        "commit": sorted(committers - admins),
+        "ticket": sorted(users - collaborators - committers - admins),
+        "collaborators": sorted(
+            [
+                {"user": u.user.user, "branches": u.branches}
+                for u in project.collaborators
+                if u not in admins and u not in committers
+            ],
+            key=lambda x: x["user"],
+        ),
+    }
+
+    # GROUPS
+    admins = set([g.group_name for g in project.admin_groups])
+    committers = set([g.group_name for g in project.committer_groups])
+    collaborators = set(
+        [g.group.group_name for g in project.collaborator_project_groups]
+    )
+    groups = set([g.group_name for g in project.groups])
+
+    output_groups = {
+        "admin": sorted(admins),
+        "commit": sorted(committers - admins),
+        "ticket": sorted(groups - collaborators - committers - admins),
+        "collaborators": sorted(
+            [
+                {"user": g.group.group_name, "branches": g.branches}
+                for g in project.collaborator_project_groups
+                if g not in admins and g not in committers
+            ],
+            key=lambda x: x["user"],
+        ),
+    }
+
+    jsonout = flask.jsonify({"users": output_users, "groups": output_groups})
+    return jsonout
+
+
 @API.route("/<repo>/delete", methods=["POST"])
 @API.route("/<namespace>/<repo>/delete", methods=["POST"])
 @API.route("/fork/<username>/<repo>/delete", methods=["POST"])
@@ -3185,3 +3522,83 @@ def delete_project(repo, username=None, namespace=None):
         {"message": "Project deleted", "project": project_json}
     )
     return jsonout
+
+
+@API.route("/<repo>/hascommit")
+@API.route("/<namespace>/<repo>/hascommit")
+@API.route("/fork/<username>/<repo>/hascommit")
+@API.route("/fork/<username>/<namespace>/<repo>/hascommit")
+@api_method
+def api_project_hascommit(repo, namespace=None, username=None):
+    """
+    Has commit on a project
+    -----------------------
+    Checks whether a specified user has commit access on a specified branch of
+    the git repo.
+
+    ::
+
+        GET /api/0/<repo>/hascommit?user=<username>&branch=<branchname>
+        GET /api/0/<namespace>/<repo>/hascommit?user=<username>&branch=<branchname>
+
+    ::
+
+        GET /api/0/fork/<username>/<repo>/hascommit?user=<username>&branch=<branchname>
+        GET /api/0/fork/<username>/<namespace>/<repo>/hascommit?user=<username>&branch=<branchname>
+
+
+    Input
+    ^^^^^
+
+    +------------------+---------+---------------+---------------------------+
+    | Key              | Type    | Optionality   | Description               |
+    +==================+=========+===============+===========================+
+    | ``user``         | String  | optional      | The username of the user  |
+    |                  |         |               | to check access for       |
+    +------------------+---------+---------------+---------------------------+
+    | ``branch``       | String  | optional      | The branch of the git repo|
+    |                  |         |               | to check access for.      |
+    |                  |         |               | Note that there is no need|
+    |                  |         |               | to specify it using       |
+    |                  |         |               | ``refs/heads/`` the name  |
+    |                  |         |               | is enough.                |
+    +------------------+---------+---------------+---------------------------+
+
+    Sample response
+    ^^^^^^^^^^^^^^^
+
+    ::
+
+        {
+          "args": {
+            "username": "pingou",
+            "branch": "main",
+            "project": {
+            }
+          },
+          "hascommit": true
+        }
+
+    """  # noqa
+
+    project = _get_repo(repo, username, namespace)
+    req_branch = flask.request.args.get("branch")
+    req_username = flask.request.args.get("user")
+    if not req_branch or not req_username:
+        raise pagure.exceptions.APIError(
+            400,
+            error_code=APIERROR.EINVALIDREQ,
+            error="Invalid input, branch or user argument missing",
+        )
+    args = {
+        "project": project.to_json(public=True),
+        "branch": req_branch,
+        "user": req_username,
+    }
+
+    hascommit = pagure.utils.is_repo_collaborator(
+        project, "refs/heads/%s" % req_branch, req_username, flask.g.session
+    )
+
+    jsonout = flask.jsonify({"args": args, "hascommit": hascommit})
+    return jsonout
diff --git a/pagure/api/user.py b/pagure/api/user.py
index 1d3ee30..bf7c45e 100644
--- a/pagure/api/user.py
+++ b/pagure/api/user.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import collections
 import datetime
@@ -20,13 +20,12 @@ import six
 import pagure
 import pagure.exceptions
 import pagure.lib.query
-from pagure.api import API, api_method, APIERROR, get_page, get_per_page
+from pagure.api import API, APIERROR, api_method, get_page, get_per_page
 from pagure.utils import is_true, validate_date, validate_date_range
 
 
 def _get_user(username):
-    """ Check user is valid or not
-    """
+    """Check user is valid or not"""
     try:
         return pagure.lib.query.get_user(flask.g.session, username)
     except pagure.exceptions.PagureException:
@@ -635,10 +634,10 @@ def api_view_user_activity_stats(username):
             # aim for noon on the desired date.
             try:
 
-                return arrow.get(d, tz).replace(hour=12).timestamp
+                return int(arrow.get(d, tz).replace(hour=12).float_timestamp)
             except (arrow.parser.ParserError, ValueError):
                 # if tz is invalid for some reason, just go with UTC
-                return arrow.get(d).replace(hour=12).timestamp
+                return int(arrow.get(d).replace(hour=12).float_timestamp)
         else:
             d = d.isoformat()
         return d
diff --git a/pagure/api/utils.py b/pagure/api/utils.py
index 49178c4..2df505a 100644
--- a/pagure/api/utils.py
+++ b/pagure/api/utils.py
@@ -8,22 +8,18 @@
 
 """
 
-from __future__ import print_function, unicode_literals, absolute_import
+from __future__ import absolute_import, print_function, unicode_literals
 
-import flask
 import logging
 
+import flask
 
 import pagure.exceptions
-
-from pagure.lib import plugins
-
-from pagure.config import config as pagure_config
 from pagure.api import APIERROR, get_authorized_api_project
-
+from pagure.config import config as pagure_config
+from pagure.lib import plugins
 from pagure.utils import api_authenticated, is_repo_committer, is_repo_user
 
-
 _log = logging.getLogger(__name__)
 
 
diff --git a/pagure/cli/admin.py b/pagure/cli/admin.py
index 99651fc..94000fd 100644
--- a/pagure/cli/admin.py
+++ b/pagure/cli/admin.py
@@ -8,18 +8,18 @@
 
 """
 
-from __future__ import print_function, unicode_literals, absolute_import
+from __future__ import absolute_import, print_function, unicode_literals
 
 import argparse
 import datetime
 import logging
 import os
-import requests
-from string import Template
 import sys
-import pygit2
+from string import Template
 
 import arrow
+import pygit2
+import requests
 from six.moves import input
 
 if "PAGURE_CONFIG" not in os.environ and os.path.exists(
@@ -31,13 +31,13 @@ if "PAGURE_CONFIG" not in os.environ and os.path.exists(
 import pagure.config  # noqa: E402
 import pagure.exceptions  # noqa: E402
 import pagure.lib.git  # noqa: E402
+import pagure.lib.model  # noqa: E402
 import pagure.lib.model_base  # noqa: E402
 import pagure.lib.query  # noqa: E402
 import pagure.lib.tasks_utils  # noqa: E402
 from pagure.flask_app import generate_user_key_files  # noqa: E402
 from pagure.utils import get_repo_path  # noqa: E402
 
-
 _config = pagure.config.reload_config()
 session = pagure.lib.model_base.create_session(_config["DB_URL"])
 _log = logging.getLogger(__name__)
@@ -53,12 +53,12 @@ WATCH = {
 
 
 def _parser_refresh_gitolite(subparser):
-    """ Set up the CLI argument parser for the refresh-gitolite action.
+    """Set up the CLI argument parser for the refresh-gitolite action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
 
-     """
+    """
     local_parser = subparser.add_parser(
         "refresh-gitolite", help="Re-generate the gitolite config file"
     )
@@ -82,7 +82,7 @@ def _parser_refresh_gitolite(subparser):
 
 
 def _parser_refresh_ssh(subparser):
-    """ Set up the CLI argument parser for the refresh-ssh action.
+    """Set up the CLI argument parser for the refresh-ssh action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -96,7 +96,7 @@ def _parser_refresh_ssh(subparser):
 
 
 def _parser_clear_hook_token(subparser):
-    """ Set up the CLI argument parser for the clear-hook-token action.
+    """Set up the CLI argument parser for the clear-hook-token action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -110,7 +110,7 @@ def _parser_clear_hook_token(subparser):
 
 
 def _parser_admin_token_list(subparser):
-    """ Set up the CLI argument parser for the admin-token list action.
+    """Set up the CLI argument parser for the admin-token list action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -145,7 +145,7 @@ def _parser_admin_token_list(subparser):
 
 
 def _parser_admin_token_info(subparser):
-    """ Set up the CLI argument parser for the admin-token info action.
+    """Set up the CLI argument parser for the admin-token info action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -159,7 +159,7 @@ def _parser_admin_token_info(subparser):
 
 
 def _parser_admin_token_expire(subparser):
-    """ Set up the CLI argument parser for the admin-token expire action.
+    """Set up the CLI argument parser for the admin-token expire action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -180,7 +180,7 @@ def _parser_admin_token_expire(subparser):
 
 
 def _parser_admin_token_create(subparser):
-    """ Set up the CLI argument parser for the admin-token create action.
+    """Set up the CLI argument parser for the admin-token create action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -198,7 +198,7 @@ def _parser_admin_token_create(subparser):
 
 
 def _parser_admin_token_update(subparser):
-    """ Set up the CLI argument parser for the admin-token update action.
+    """Set up the CLI argument parser for the admin-token update action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -220,7 +220,7 @@ def _parser_admin_token_update(subparser):
 
 
 def _parser_admin_token(subparser):
-    """ Set up the CLI argument parser for the admin-token action.
+    """Set up the CLI argument parser for the admin-token action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -245,7 +245,7 @@ def _parser_admin_token(subparser):
 
 
 def _parser_get_watch(subparser):
-    """ Set up the CLI argument parser for the get-watch action.
+    """Set up the CLI argument parser for the get-watch action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -265,7 +265,7 @@ def _parser_get_watch(subparser):
 
 
 def _parser_update_watch(subparser):
-    """ Set up the CLI argument parser for the update-watch action.
+    """Set up the CLI argument parser for the update-watch action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -290,7 +290,7 @@ def _parser_update_watch(subparser):
 
 
 def _parser_read_only(subparser):
-    """ Set up the CLI argument parser for the read-only action.
+    """Set up the CLI argument parser for the read-only action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -316,7 +316,7 @@ def _parser_read_only(subparser):
 
 
 def _parser_new_group(subparser):
-    """ Set up the CLI argument parser for the new-group action.
+    """Set up the CLI argument parser for the new-group action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -339,7 +339,7 @@ def _parser_new_group(subparser):
 
 
 def _parser_list_groups(subparser):
-    """ Set up the CLI argument parser for the list-groups action.
+    """Set up the CLI argument parser for the list-groups action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -352,7 +352,7 @@ def _parser_list_groups(subparser):
 
 
 def _parser_block_user(subparser):
-    """ Set up the CLI argument parser for the block-user action.
+    """Set up the CLI argument parser for the block-user action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
@@ -383,7 +383,7 @@ def _parser_block_user(subparser):
 
 
 def _parser_upload_repospanner_hooks(subparser):
-    """ Set up the CLI argument parser to upload repospanner hook.
+    """Set up the CLI argument parser to upload repospanner hook.
 
     Args:
         subparser: An argparse subparser
@@ -398,7 +398,7 @@ def _parser_upload_repospanner_hooks(subparser):
 
 
 def _parser_ensure_project_hooks(subparser):
-    """ Set up the CLI argument parser to ensure project hooks are setup
+    """Set up the CLI argument parser to ensure project hooks are setup
 
     Args:
         subparser: An argparse subparser
@@ -414,12 +414,12 @@ def _parser_ensure_project_hooks(subparser):
 
 
 def _parser_delete_project(subparser):
-    """ Set up the CLI argument parser for the delete-project action.
+    """Set up the CLI argument parser for the delete-project action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
 
-     """
+    """
     local_parser = subparser.add_parser(
         "delete-project", help="Delete the project specified"
     )
@@ -440,12 +440,12 @@ def _parser_delete_project(subparser):
 
 
 def _parser_create_branch(subparser):
-    """ Set up the CLI argument parser for the create-branch action.
+    """Set up the CLI argument parser for the create-branch action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
 
-     """
+    """
     local_parser = subparser.add_parser(
         "create-branch",
         help="Create the specified branch in the specified project",
@@ -480,12 +480,12 @@ def _parser_create_branch(subparser):
 
 
 def _parser_set_default_branch(subparser):
-    """ Set up the CLI argument parser for the set-default-branch action.
+    """Set up the CLI argument parser for the set-default-branch action.
 
     :arg subparser: an argparse subparser allowing to have action's specific
         arguments
 
-     """
+    """
     local_parser = subparser.add_parser(
         "set-default-branch", help="Set the specified branch as default"
     )
@@ -503,8 +503,25 @@ def _parser_set_default_branch(subparser):
     local_parser.set_defaults(func=do_set_default_branch)
 
 
+def _parser_update_acls(subparser):
+    """Set up the CLI argument parser for the update-acls action.
+
+    :arg subparser: an argparse subparser allowing to have action's specific
+        arguments
+
+    """
+
+    local_parser = subparser.add_parser(
+        "update-acls",
+        help="Update the ACLs stored in the database with the ones defined "
+        "in the configuration file (addition only, no ACLs are removed from "
+        "the database).",
+    )
+    local_parser.set_defaults(func=do_update_acls)
+
+
 def parse_arguments(args=None):
-    """ Set-up the argument parsing. """
+    """Set-up the argument parsing."""
     parser = argparse.ArgumentParser(
         description="The admin CLI for this pagure instance"
     )
@@ -567,24 +584,25 @@ def parse_arguments(args=None):
     # set-default-branch
     _parser_set_default_branch(subparser)
 
+    # update-acls
+    _parser_update_acls(subparser)
+
     return parser.parse_args(args)
 
 
 def _ask_confirmation():
-    """ Ask to confirm an action.
-    """
+    """Ask to confirm an action."""
     action = input("Do you want to continue? [y/N]")
     return action.lower() in ["y", "yes"]
 
 
 def _get_input(text):
-    """ Ask the user for input. """
+    """Ask the user for input."""
     return input(text)
 
 
 def _get_project(arg_project, user=None):
-    """ From the project specified to the CLI, extract the actual project.
-    """
+    """From the project specified to the CLI, extract the actual project."""
     namespace = None
     if "/" in arg_project:
         if arg_project.count("/") > 1:
@@ -601,7 +619,7 @@ def _get_project(arg_project, user=None):
 
 
 def _check_project(_project, **kwargs):
-    """ Check that the project extracted with args is a valid project """
+    """Check that the project extracted with args is a valid project"""
     if _project is None:
         raise pagure.exceptions.PagureException(
             "No project found with: {}".format(
@@ -613,7 +631,7 @@ def _check_project(_project, **kwargs):
 
 
 def do_generate_acl(args):
-    """ Regenerate the gitolite ACL file.
+    """Regenerate the gitolite ACL file.
 
 
     :arg args: the argparse object returned by ``parse_arguments()``.
@@ -667,7 +685,7 @@ def do_generate_acl(args):
 
 
 def do_refresh_ssh(_):
-    """ Regenerate the user key files.
+    """Regenerate the user key files.
 
     :arg _: the argparse object returned by ``parse_arguments()``, which is
         ignored as there are no argument to pass to this action.
@@ -685,7 +703,7 @@ def do_refresh_ssh(_):
 
 
 def do_generate_hook_token(_):
-    """ Regenerate the hook_token for each projects in the DB.
+    """Regenerate the hook_token for each projects in the DB.
 
     :arg _: the argparse object returned by ``parse_arguments()``, which is
         ignored as there are no argument to pass to this action.
@@ -702,7 +720,7 @@ def do_generate_hook_token(_):
 
 
 def do_list_admin_token(args):
-    """ List the admin token.
+    """List the admin token.
 
     :arg args: the argparse object returned by ``parse_arguments()``.
 
@@ -727,7 +745,7 @@ def do_list_admin_token(args):
 
 
 def do_info_admin_token(args):
-    """ Print out information about the specified API token.
+    """Print out information about the specified API token.
 
     :arg args: the argparse object returned by ``parse_arguments()``.
 
@@ -745,7 +763,7 @@ def do_info_admin_token(args):
 
 
 def do_expire_admin_token(args):
-    """ Expire a specific admin token.
+    """Expire a specific admin token.
 
     :arg args: the argparse object returned by ``parse_arguments()``.
 
@@ -774,7 +792,7 @@ def do_expire_admin_token(args):
 
 
 def do_update_admin_token(args):
-    """ Update the expiration date of an admin token.
+    """Update the expiration date of an admin token.
 
     :arg args: the argparse object returned by ``parse_arguments()``.
 
@@ -822,7 +840,7 @@ def do_update_admin_token(args):
 
 
 def do_create_admin_token(args):
-    """ Create a new admin token.
+    """Create a new admin token.
 
     :arg args: the argparse object returned by ``parse_arguments()``.
 
@@ -871,7 +889,7 @@ def do_create_admin_token(args):
 
 
 def do_delete_project(args):
-    """ Delete a project.
+    """Delete a project.
 
     :arg args: the argparse object returned by ``parse_arguments()``.
 
@@ -906,8 +924,24 @@ def do_delete_project(args):
     print("Project deleted")
 
 
+def do_update_acls(args):
+    """Update the ACLs in the database from the list present in the
+    configuration file.
+
+    :arg args: the argparse object returned by ``parse_arguments()``.
+
+    """
+    acls = _config.get("ACLS", {})
+    _log.debug("ACLS:       %s", acls)
+
+    pagure.lib.model.create_default_status(session, acls=acls)
+    print(
+        "ACLS in the database synced with the list in the configuration file"
+    )
+
+
 def do_get_watch_status(args):
-    """ Get the watch status of an user on a project.
+    """Get the watch status of an user on a project.
 
     :arg args: the argparse object returned by ``parse_arguments()``.
 
@@ -944,7 +978,7 @@ def do_get_watch_status(args):
 
 
 def do_update_watch_status(args):
-    """ Update the watch status of an user on a project.
+    """Update the watch status of an user on a project.
 
     :arg args: the argparse object returned by ``parse_arguments()``.
 
@@ -988,7 +1022,7 @@ def do_update_watch_status(args):
 
 
 def do_read_only(args):
-    """ Set or update the read-only status of a project.
+    """Set or update the read-only status of a project.
 
     :arg args: the argparse object returned by ``parse_arguments()``.
 
@@ -1030,7 +1064,7 @@ def do_read_only(args):
 
 
 def do_new_group(args):
-    """ Create a new group in this pagure instance.
+    """Create a new group in this pagure instance.
 
     :arg args: the argparse object returned by ``parse_arguments()``.
 
@@ -1075,7 +1109,7 @@ def do_new_group(args):
 
 
 def do_list_groups(args):
-    """ Lists existing groups in this pagure instance.
+    """Lists existing groups in this pagure instance.
 
     :arg args: the argparse object returned by ``parse_arguments()``.
 
@@ -1091,7 +1125,7 @@ def do_list_groups(args):
 
 
 def do_list_blocked_users(args):
-    """ List all the blocked users.
+    """List all the blocked users.
 
     :arg args: the argparse object returned by ``parse_arguments()``.
 
@@ -1126,7 +1160,7 @@ def do_list_blocked_users(args):
 
 
 def do_block_user(args):
-    """ Block the specified user from all interactions with pagure until the
+    """Block the specified user from all interactions with pagure until the
     specified date.
 
     :arg args: the argparse object returned by ``parse_arguments()``.
@@ -1170,7 +1204,7 @@ def do_block_user(args):
 
 
 def do_upload_repospanner_hooks(args):
-    """ Upload hooks to repoSpanner
+    """Upload hooks to repoSpanner
 
     Args:
         args (argparse.Namespace): Parsed arguments
@@ -1216,7 +1250,7 @@ def do_upload_repospanner_hooks(args):
 
 
 def do_ensure_project_hooks(args):
-    """ Ensures that all projects have their hooks setup
+    """Ensures that all projects have their hooks setup
 
     Args:
         args (argparse.Namespace): Parsed arguments
@@ -1235,7 +1269,7 @@ def do_ensure_project_hooks(args):
 
 
 def do_create_branch(args):
-    """ Creates the specified git branch
+    """Creates the specified git branch
 
     Args:
         args (argparse.Namespace): Parsed arguments
@@ -1286,7 +1320,7 @@ def do_create_branch(args):
 
 
 def do_set_default_branch(args):
-    """ Sets the specified git branch as default
+    """Sets the specified git branch as default
 
     Args:
         args (argparse.Namespace): Parsed arguments
@@ -1314,7 +1348,7 @@ def do_set_default_branch(args):
 
 
 def main():
-    """ Start of the application. """
+    """Start of the application."""
 
     # Parse the arguments
     args = parse_arguments()
diff --git a/pagure/config.py b/pagure/config.py
index b6563c4..79714e9 100644
--- a/pagure/config.py
+++ b/pagure/config.py
@@ -8,14 +8,15 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import os  # noqa: E402
+
 import flask  # noqa: E402
 
 
 def reload_config():
-    """ Reload the configuration. """
+    """Reload the configuration."""
     config = flask.config.Config(
         os.path.dirname(os.path.abspath(__file__)), flask.Flask.default_config
     )
diff --git a/pagure/decorators.py b/pagure/decorators.py
index 5fc3f10..7d5583c 100644
--- a/pagure/decorators.py
+++ b/pagure/decorators.py
@@ -9,11 +9,13 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
+
+from functools import wraps
 
 import flask
+
 from pagure.flask_app import admin_session_timedout
-from functools import wraps
 
 
 def has_issue_tracker(function):
diff --git a/pagure/default_config.py b/pagure/default_config.py
index 045f270..d8a7ee0 100644
--- a/pagure/default_config.py
+++ b/pagure/default_config.py
@@ -8,13 +8,12 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import os
 from datetime import timedelta
 
-from pagure.mail_logging import ContextInjector, MSG_FORMAT
-
+from pagure.mail_logging import MSG_FORMAT, ContextInjector  # noqa: I101
 
 # Set the time after which the admin session expires
 ADMIN_SESSION_LIFETIME = timedelta(minutes=20)
@@ -78,6 +77,9 @@ ENABLE_GROUP_MNGT = True
 # Enables / Disables private projects
 PRIVATE_PROJECTS = True
 
+# Enable / Disable user registration (local auth only)
+ALLOW_USER_REGISTRATION = True
+
 # Enable / Disable deleting branches in the UI
 ALLOW_DELETE_BRANCH = True
 
@@ -280,7 +282,7 @@ SESSION_COOKIE_NAME = "pagure"
 CHECK_SESSION_IP = True
 
 # Lenght for short commits ids or file hex
-SHORT_LENGTH = 6
+SHORT_LENGTH = 7
 
 # Used by SESSION_COOKIE_PATH
 APPLICATION_ROOT = "/"
@@ -355,6 +357,9 @@ ACLS = {
     "pull_request_rebase": "Rebase a pull-request",
     "tag_project": "Allows adding git tags to a project",
     "commit": "Commit to a git repository via http(s)",
+    "modify_git_alias": "Modify git aliases (create or delete)",
+    "create_git_alias": "Create git aliases",
+    "delete_git_alias": "Delete git aliases",
 }
 
 # List of ACLs which a regular user is allowed to associate to an API token
@@ -637,3 +642,17 @@ CSP_HEADERS = (
     "base-uri 'self';"
     "img-src 'self' https:;"
 )
+
+PR_WARN_CHARACTERS = set(
+    [
+        chr(0x202A),
+        chr(0x202B),
+        chr(0x202C),
+        chr(0x202D),
+        chr(0x202E),
+        chr(0x2066),
+        chr(0x2067),
+        chr(0x2068),
+        chr(0x2069),
+    ]
+)
diff --git a/pagure/doc_utils.py b/pagure/doc_utils.py
index 3238120..5222267 100644
--- a/pagure/doc_utils.py
+++ b/pagure/doc_utils.py
@@ -10,7 +10,9 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
+
+import textwrap
 
 import docutils
 import docutils.core
@@ -18,15 +20,14 @@ import docutils.examples
 import jinja2
 import kitchen.text.converters as ktc
 import markupsafe
-import textwrap
 
-from pagure.config import config as pagure_config
-import pagure.lib.query
 import pagure.lib.encoding_utils
+import pagure.lib.query
+from pagure.config import config as pagure_config
 
 
 def modify_rst(rst, view_file_url=None):
-    """ Downgrade some of our rst directives if docutils is too old. """
+    """Downgrade some of our rst directives if docutils is too old."""
     if view_file_url:
         rst = rst.replace(".. image:: ", ".. image:: %s" % view_file_url)
 
@@ -56,8 +57,7 @@ def modify_rst(rst, view_file_url=None):
 
 
 def modify_html(html):
-    """ Perform style substitutions where docutils doesn't do what we want.
-    """
+    """Perform style substitutions where docutils doesn't do what we want."""
 
     substitutions = {
         '<tt class="docutils literal">': "<code>",
@@ -76,7 +76,7 @@ def modify_html(html):
 
 
 def convert_doc(rst_string, view_file_url=None):
-    """ Utility to load an RST file and turn it into fancy HTML. """
+    """Utility to load an RST file and turn it into fancy HTML."""
     rst = modify_rst(rst_string, view_file_url)
 
     overrides = {"report_level": "quiet"}
@@ -98,7 +98,7 @@ def convert_doc(rst_string, view_file_url=None):
 
 
 def convert_readme(content, ext, view_file_url=None):
-    """ Convert the provided content according to the extension of the file
+    """Convert the provided content according to the extension of the file
     provided.
     """
     output = pagure.lib.encoding_utils.decode(ktc.to_bytes(content))
@@ -116,7 +116,7 @@ def convert_readme(content, ext, view_file_url=None):
 
 
 def load_doc(endpoint):
-    """ Utility to load an RST file and turn it into fancy HTML. """
+    """Utility to load an RST file and turn it into fancy HTML."""
 
     rst = modify_rst(textwrap.dedent(endpoint.__doc__))
 
@@ -129,7 +129,7 @@ def load_doc(endpoint):
 
 
 def load_doc_title(endpoint):
-    """ Utility to load docstring title from a method """
+    """Utility to load docstring title from a method"""
     rst = modify_rst(textwrap.dedent(endpoint.__doc__))
 
     parts = docutils.examples.html_parts(rst)
@@ -139,7 +139,7 @@ def load_doc_title(endpoint):
 
 
 def load_doc_title_and_name(endpoint):
-    """ Utility to load the HTML doc version and the title from a method. """
+    """Utility to load the HTML doc version and the title from a method."""
 
     result = {
         "doc": load_doc(endpoint),
diff --git a/pagure/docs_server.py b/pagure/docs_server.py
index dc5e297..21faf4e 100644
--- a/pagure/docs_server.py
+++ b/pagure/docs_server.py
@@ -8,24 +8,23 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import logging
 import os
 
 import flask
 import pygit2
-
 from binaryornot.helpers import is_binary_string
 from whitenoise import WhiteNoise
 
 import pagure.config
 import pagure.doc_utils
 import pagure.exceptions
+import pagure.forms
 import pagure.lib.mimetype
 import pagure.lib.model_base
 import pagure.lib.query
-import pagure.forms
 
 # Create the application.
 APP = flask.Flask(__name__)
@@ -81,7 +80,7 @@ TMPL_HTML = """
 
 
 def __get_tree(repo_obj, tree, filepath, index=0, extended=False):
-    """ Retrieve the entry corresponding to the provided filename in a
+    """Retrieve the entry corresponding to the provided filename in a
     given tree.
     """
     filename = filepath[index]
@@ -116,7 +115,7 @@ def __get_tree(repo_obj, tree, filepath, index=0, extended=False):
 
 
 def __get_tree_and_content(repo_obj, commit, path):
-    """ Return the tree and the content of the specified file. """
+    """Return the tree and the content of the specified file."""
 
     (blob_or_tree, tree_obj, extended) = __get_tree(
         repo_obj, commit.tree, path
@@ -164,8 +163,7 @@ def __get_tree_and_content(repo_obj, commit, path):
 @APP.route("/fork/<username>/<repo>/<path:filename>")
 @APP.route("/fork/<namespace>.<username>/<repo>/<path:filename>")
 def view_docs(repo, username=None, namespace=None, filename=None):
-    """ Display the documentation
-    """
+    """Display the documentation"""
     if "." in repo:
         namespace, repo = repo.split(".", 1)
 
diff --git a/pagure/exceptions.py b/pagure/exceptions.py
index ed2fcc4..47e5620 100644
--- a/pagure/exceptions.py
+++ b/pagure/exceptions.py
@@ -8,11 +8,11 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 
 class PagureException(Exception):
-    """ Parent class of all the exception for all Pagure specific
+    """Parent class of all the exception for all Pagure specific
     exceptions.
     """
 
@@ -20,7 +20,7 @@ class PagureException(Exception):
 
 
 class RepoExistsException(PagureException):
-    """ Exception thrown when trying to create a repository that already
+    """Exception thrown when trying to create a repository that already
     exists.
     """
 
@@ -28,7 +28,7 @@ class RepoExistsException(PagureException):
 
 
 class ProjectBlackListedException(PagureException):
-    """ Exception thrown when trying to create a repository but, that repository
+    """Exception thrown when trying to create a repository but, that repository
     name has been blacklisted
     """
 
@@ -36,14 +36,13 @@ class ProjectBlackListedException(PagureException):
 
 
 class AccessLevelNotFound(PagureException):
-    """ Exception raised when the access level asked is not allowed on pagure
-    """
+    """Exception raised when the access level asked is not allowed on pagure"""
 
     pass
 
 
 class FileNotFoundException(PagureException):
-    """ Exception thrown when the desired file is not found.
+    """Exception thrown when the desired file is not found.
 
     This exception is found when the file is searched in a git repo or when
     setting up one of the git hook.
@@ -54,7 +53,7 @@ class FileNotFoundException(PagureException):
 
 
 class APIError(PagureException):
-    """ Exception raised by the API when something goes wrong. """
+    """Exception raised by the API when something goes wrong."""
 
     def __init__(self, status_code, error_code, error=None, errors=None):
         self.status_code = status_code
@@ -64,7 +63,7 @@ class APIError(PagureException):
 
 
 class BranchNotFoundException(PagureException):
-    """ Exception thrown when trying to use a branch that could not be
+    """Exception thrown when trying to use a branch that could not be
     found in a repository.
     """
 
@@ -72,14 +71,13 @@ class BranchNotFoundException(PagureException):
 
 
 class PagureEvException(PagureException):
-    """ Exceptions used in the pagure_stream_server.
-    """
+    """Exceptions used in the pagure_stream_server."""
 
     pass
 
 
 class GitConflictsException(PagureException):
-    """ Exception used when trying to pull on a repo and that leads to
+    """Exception used when trying to pull on a repo and that leads to
     conflicts.
     """
 
@@ -87,26 +85,26 @@ class GitConflictsException(PagureException):
 
 
 class HookInactiveException(PagureException):
-    """ Exception raised when the hook is inactive. """
+    """Exception raised when the hook is inactive."""
 
     pass
 
 
 class NoCorrespondingPR(PagureException):
-    """ Exception raised when no pull-request is found with the given
-    information. """
+    """Exception raised when no pull-request is found with the given
+    information."""
 
     pass
 
 
 class InvalidObjectException(PagureException):
-    """ Exception raised when a given object is not what was expected. """
+    """Exception raised when a given object is not what was expected."""
 
     pass
 
 
 class PagureEncodingException(PagureException, ValueError):
-    """ Exception raised none of the encoding guessed could be applied to
+    """Exception raised none of the encoding guessed could be applied to
     the content examined
     """
 
@@ -114,18 +112,18 @@ class PagureEncodingException(PagureException, ValueError):
 
 
 class PagurePushDenied(PagureException):
-    """ Exception raised if a remote hook rejected a push """
+    """Exception raised if a remote hook rejected a push"""
 
     pass
 
 
 class InvalidTimestampException(PagureException):
-    """ Exception raised when the hook is inactive. """
+    """Exception raised when the hook is inactive."""
 
     pass
 
 
 class InvalidDateformatException(PagureException):
-    """ Exception raised when the hook is inactive. """
+    """Exception raised when the hook is inactive."""
 
     pass
diff --git a/pagure/flask_app.py b/pagure/flask_app.py
index efeb564..ecbe0f8 100644
--- a/pagure/flask_app.py
+++ b/pagure/flask_app.py
@@ -8,19 +8,19 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import datetime
 import gc
 import logging
+import os
 import string
 import time
-import os
 import warnings
 
 import flask
 import pygit2
-
+from six.moves.urllib.parse import urljoin
 from whitenoise import WhiteNoise
 
 import pagure.doc_utils
@@ -61,7 +61,7 @@ if pagure_config.get("PAGURE_CI_SERVICES"):
 
 
 def create_app(config=None):
-    """ Create the flask application. """
+    """Create the flask application."""
     app = flask.Flask(__name__)
     app.config = pagure_config
 
@@ -99,7 +99,7 @@ def create_app(config=None):
         FAS.init_app(app)
     elif auth == "oidc":
         # Only import and set flask_fas_openid if it is needed
-        from pagure.ui.oidc_login import oidc, fas_user_from_oidc
+        from pagure.ui.oidc_login import fas_user_from_oidc, oidc
 
         oidc.init_app(app)
         app.before_request(fas_user_from_oidc)
@@ -210,8 +210,7 @@ def create_app(config=None):
 
 
 def generate_user_key_files():
-    """ Regenerate the key files used by gitolite.
-    """
+    """Regenerate the key files used by gitolite."""
     gitolite_home = pagure_config.get("GITOLITE_HOME", None)
     if gitolite_home:
         users = pagure.lib.query.search_user(flask.g.session)
@@ -227,7 +226,7 @@ def generate_user_key_files():
 
 
 def admin_session_timedout():
-    """ Check if the current user has been authenticated for more than what
+    """Check if the current user has been authenticated for more than what
     is allowed (defaults to 15 minutes).
     If it is the case, the user is logged out and the method returns True,
     otherwise it returns False.
@@ -248,8 +247,7 @@ def admin_session_timedout():
 
 
 def logout():
-    """ Log out the user currently logged in in the application
-    """
+    """Log out the user currently logged in in the application"""
     auth = pagure_config.get("PAGURE_AUTH", None)
     if auth in ["fas", "openid"]:
         if hasattr(flask.g, "fas_user") and flask.g.fas_user is not None:
@@ -267,7 +265,7 @@ def logout():
 
 
 def set_request():
-    """ Prepare every request. """
+    """Prepare every request."""
     flask.session.permanent = True
     if not hasattr(flask.g, "session") or not flask.g.session:
         flask.g.session = pagure.lib.model_base.create_session(
@@ -442,17 +440,19 @@ def set_request():
 
 
 def auth_login():  # pragma: no cover
-    """ Method to log into the application using FAS OpenID. """
+    """Method to log into the application using FAS OpenID."""
     return_point = flask.url_for("ui_ns.index")
     if "next" in flask.request.args:
         if pagure.utils.is_safe_url(flask.request.args["next"]):
-            return_point = flask.request.args["next"]
+            return_point = urljoin(
+                flask.request.host_url, flask.request.args["next"]
+            )
 
     authenticated = pagure.utils.authenticated()
     auth = pagure_config.get("PAGURE_AUTH", None)
 
     if not authenticated and auth == "oidc":
-        from pagure.ui.oidc_login import oidc, fas_user_from_oidc, set_user
+        from pagure.ui.oidc_login import fas_user_from_oidc, oidc, set_user
 
         # If oidc is used and user hits this endpoint, it will redirect
         # to IdP with destination=<pagure>/login?next=<location>
@@ -492,6 +492,8 @@ def auth_login():  # pragma: no cover
                 )
             ]
         groups = set(groups).union(admins)
+        if auth == "fas":
+            groups.add("signed_fpca")
         ext_committer = set(pagure_config.get("EXTERNAL_COMMITTER", {}))
         groups = set(groups).union(ext_committer)
         flask.g.unsafe_javascript = True
@@ -504,11 +506,13 @@ def auth_login():  # pragma: no cover
 
 
 def auth_logout():  # pragma: no cover
-    """ Method to log out from the application. """
+    """Method to log out from the application."""
     return_point = flask.url_for("ui_ns.index")
     if "next" in flask.request.args:
         if pagure.utils.is_safe_url(flask.request.args["next"]):
-            return_point = flask.request.args["next"]
+            return_point = urljoin(
+                flask.request.host_url, flask.request.args["next"]
+            )
 
     if not pagure.utils.authenticated():
         return flask.redirect(return_point)
@@ -521,7 +525,7 @@ def auth_logout():  # pragma: no cover
 
 # pylint: disable=unused-argument
 def end_request(exception=None):
-    """ This method is called at the end of each request.
+    """This method is called at the end of each request.
 
     Remove the DB session at the end of each request.
     Runs a garbage collection to get rid of any open pygit2 handles.
@@ -533,7 +537,7 @@ def end_request(exception=None):
 
 
 def after_request(response):
-    """ After request callback, adjust the headers returned """
+    """After request callback, adjust the headers returned"""
     if not hasattr(flask.g, "nonce"):
         return response
 
@@ -555,8 +559,7 @@ def after_request(response):
 
 
 def _get_user(username):
-    """ Check if user exists or not
-    """
+    """Check if user exists or not"""
     try:
         return pagure.lib.query.get_user(flask.g.session, username)
     except pagure.exceptions.PagureException as e:
diff --git a/pagure/forms.py b/pagure/forms.py
index 366d8ae..ec21983 100644
--- a/pagure/forms.py
+++ b/pagure/forms.py
@@ -12,7 +12,7 @@
 # pylint: disable=no-init
 # pylint: disable=super-on-old-class
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import datetime
 import re
@@ -31,7 +31,7 @@ import wtforms
 import pagure.lib.query
 import pagure.validators
 from pagure.config import config as pagure_config
-from pagure.utils import urlpattern, is_admin
+from pagure.utils import is_admin, urlpattern
 
 STRICT_REGEX = "^[a-zA-Z0-9-_]+$"
 # This regex is used when creating tags, there we do not want to allow ','
@@ -49,7 +49,7 @@ if hasattr(wtf, "__version__"):
 
 
 class PagureForm(FlaskForm):
-    """ Local form allowing us to form set the time limit. """
+    """Local form allowing us to form set the time limit."""
 
     def __init__(self, *args, **kwargs):
         delta = pagure_config.get("WTF_CSRF_TIME_LIMIT", 3600)
@@ -65,7 +65,7 @@ class PagureForm(FlaskForm):
 
 
 def convert_value(val):
-    """ Convert the provided values to strings when possible. """
+    """Convert the provided values to strings when possible."""
     if val:
         if not isinstance(val, (list, tuple, six.text_type)):
             return val.decode("utf-8")
@@ -74,7 +74,7 @@ def convert_value(val):
 
 
 class MultipleEmail(wtforms.validators.Email):
-    """ Split the value by comma and run them through the email validator
+    """Split the value by comma and run them through the email validator
     of wtforms.
     """
 
@@ -87,15 +87,14 @@ class MultipleEmail(wtforms.validators.Email):
 
 
 def user_namespace_if_private(form, field):
-    """ Check if the data in the field is the same as in the password field.
-    """
+    """Check if the data in the field is the same as in the password field."""
     if form.private.data:
         field.data = flask.g.fas_user.username
 
 
 def file_virus_validator(form, field):
-    """ Checks for virus in the file from flask request object,
-    raises wtf.ValidationError if virus is found else None. """
+    """Checks for virus in the file from flask request object,
+    raises wtf.ValidationError if virus is found else None."""
 
     if not pagure_config["VIRUS_SCAN_ATTACHMENTS"]:
         return
@@ -127,16 +126,16 @@ def file_virus_validator(form, field):
 
 
 def ssh_key_validator(form, field):
-    """ Form for ssh key validation """
+    """Form for ssh key validation"""
     if not pagure.lib.query.are_valid_ssh_keys(field.data):
         raise wtforms.ValidationError("Invalid SSH keys")
 
 
 class ProjectFormSimplified(PagureForm):
-    """ Form to edit the description of a project. """
+    """Form to edit the description of a project."""
 
     description = wtforms.StringField(
-        'Description <span class="error">*</span>',
+        "Description",
         [wtforms.validators.DataRequired()],
     )
     url = wtforms.StringField(
@@ -167,9 +166,9 @@ class ProjectFormSimplified(PagureForm):
 
 
 class ProjectForm(ProjectFormSimplified):
-    """ Form to create or edit project. """
+    """Form to create or edit project."""
 
-    name = wtforms.StringField('Project name <span class="error">*</span>')
+    name = wtforms.StringField("Project name")
     mirrored_from = wtforms.StringField(
         "Mirror from URL",
         [
@@ -207,11 +206,12 @@ class ProjectForm(ProjectFormSimplified):
         default=pagure_config["REPOSPANNER_NEW_REPO"],
     )
     default_branch = wtforms.StringField(
-        "Default branch", [wtforms.validators.optional()],
+        "Default branch",
+        [wtforms.validators.optional()],
     )
 
     def __init__(self, *args, **kwargs):
-        """ Calls the default constructor with the normal argument but
+        """Calls the default constructor with the normal argument but
         uses the list of collection provided to fill the choices of the
         drop-down list.
         """
@@ -249,14 +249,14 @@ class ProjectForm(ProjectFormSimplified):
 
 
 class IssueFormSimplied(PagureForm):
-    """ Form to create or edit an issue. """
+    """Form to create or edit an issue."""
 
     title = wtforms.StringField(
-        'Title<span class="error">*</span>',
+        "Title",
         [wtforms.validators.DataRequired()],
     )
     issue_content = wtforms.TextAreaField(
-        'Content<span class="error">*</span>',
+        "Content",
         [wtforms.validators.DataRequired()],
     )
     private = wtforms.BooleanField(
@@ -276,7 +276,7 @@ class IssueFormSimplied(PagureForm):
     )
 
     def __init__(self, *args, **kwargs):
-        """ Calls the default constructor with the normal argument but
+        """Calls the default constructor with the normal argument but
         uses the list of collection provided to fill the choices of the
         drop-down list.
         """
@@ -295,14 +295,14 @@ class IssueFormSimplied(PagureForm):
 
 
 class IssueForm(IssueFormSimplied):
-    """ Form to create or edit an issue. """
+    """Form to create or edit an issue."""
 
     status = wtforms.SelectField(
         "Status", [wtforms.validators.DataRequired()], choices=[]
     )
 
     def __init__(self, *args, **kwargs):
-        """ Calls the default constructor with the normal argument but
+        """Calls the default constructor with the normal argument but
         uses the list of collection provided to fill the choices of the
         drop-down list.
         """
@@ -314,10 +314,10 @@ class IssueForm(IssueFormSimplied):
 
 
 class RequestPullForm(PagureForm):
-    """ Form to create a pull request. """
+    """Form to create a pull request."""
 
     title = wtforms.StringField(
-        'Title<span class="error">*</span>',
+        "Title",
         [wtforms.validators.DataRequired()],
     )
     initial_comment = wtforms.TextAreaField(
@@ -330,28 +330,50 @@ class RequestPullForm(PagureForm):
     )
 
 
+class RequestPullEditForm(RequestPullForm):
+    """Form to edit a pull request."""
+
+    branch_to = wtforms.SelectField(
+        "Target branch",
+        [wtforms.validators.DataRequired()],
+        choices=[],
+        coerce=convert_value,
+    )
+
+    def __init__(self, *args, **kwargs):
+        """Calls the default constructor with the normal argument but
+        uses the list of collection provided to fill the choices of the
+        drop-down list.
+        """
+        super(RequestPullEditForm, self).__init__(*args, **kwargs)
+        if "branches" in kwargs:
+            self.branch_to.choices = [
+                (branch, branch) for branch in kwargs["branches"]
+            ]
+
+
 class RemoteRequestPullForm(RequestPullForm):
-    """ Form to create a remote pull request. """
+    """Form to create a remote pull request."""
 
     git_repo = wtforms.StringField(
-        'Git repo address<span class="error">*</span>',
+        "Git repo address",
         [
             wtforms.validators.DataRequired(),
             wtforms.validators.Regexp(urlpattern, flags=re.IGNORECASE),
         ],
     )
     branch_from = wtforms.StringField(
-        'Git branch<span class="error">*</span>',
+        "Git branch",
         [wtforms.validators.DataRequired()],
     )
     branch_to = wtforms.StringField(
-        'Git branch to merge in<span class="error">*</span>',
+        "Git branch to merge in",
         [wtforms.validators.DataRequired()],
     )
 
 
 class DeleteIssueTagForm(PagureForm):
-    """ Form to remove a tag to from a project. """
+    """Form to remove a tag to from a project."""
 
     tag = wtforms.StringField(
         "Tag",
@@ -364,7 +386,7 @@ class DeleteIssueTagForm(PagureForm):
 
 
 class AddIssueTagForm(DeleteIssueTagForm):
-    """ Form to add a tag to a project. """
+    """Form to add a tag to a project."""
 
     tag_description = wtforms.StringField(
         "Tag Description", [wtforms.validators.Optional()]
@@ -375,7 +397,7 @@ class AddIssueTagForm(DeleteIssueTagForm):
 
 
 class ApiAddIssueTagForm(PagureForm):
-    """ Form to add a tag to a project from the API endpoint """
+    """Form to add a tag to a project from the API endpoint"""
 
     tag = wtforms.StringField(
         "Tag",
@@ -395,7 +417,7 @@ class ApiAddIssueTagForm(PagureForm):
 
 
 class StatusForm(PagureForm):
-    """ Form to add/change the status of an issue. """
+    """Form to add/change the status of an issue."""
 
     status = wtforms.SelectField(
         "Status", [wtforms.validators.DataRequired()], choices=[]
@@ -405,7 +427,7 @@ class StatusForm(PagureForm):
     )
 
     def __init__(self, *args, **kwargs):
-        """ Calls the default constructor with the normal argument but
+        """Calls the default constructor with the normal argument but
         uses the list of collection provided to fill the choices of the
         drop-down list.
         """
@@ -422,7 +444,7 @@ class StatusForm(PagureForm):
 
 
 class MilestoneForm(PagureForm):
-    """ Form to change the milestone of an issue. """
+    """Form to change the milestone of an issue."""
 
     milestone = wtforms.SelectField(
         "Milestone",
@@ -432,7 +454,7 @@ class MilestoneForm(PagureForm):
     )
 
     def __init__(self, *args, **kwargs):
-        """ Calls the default constructor with the normal argument but
+        """Calls the default constructor with the normal argument but
         uses the list of collection provided to fill the choices of the
         drop-down list.
         """
@@ -445,7 +467,7 @@ class MilestoneForm(PagureForm):
 
 
 class NewTokenForm(PagureForm):
-    """ Form to add a new token. """
+    """Form to add a new token."""
 
     description = wtforms.StringField(
         "description", [wtforms.validators.Optional()]
@@ -460,7 +482,7 @@ class NewTokenForm(PagureForm):
     )
 
     def __init__(self, *args, **kwargs):
-        """ Calls the default constructor with the normal argument but
+        """Calls the default constructor with the normal argument but
         uses the list of collection provided to fill the choices of the
         drop-down list.
         """
@@ -474,7 +496,7 @@ class NewTokenForm(PagureForm):
 
 
 class UpdateIssueForm(PagureForm):
-    """ Form to add a comment to an issue. """
+    """Form to add a comment to an issue."""
 
     tag = wtforms.StringField(
         "tag",
@@ -517,7 +539,7 @@ class UpdateIssueForm(PagureForm):
     )
 
     def __init__(self, *args, **kwargs):
-        """ Calls the default constructor with the normal argument but
+        """Calls the default constructor with the normal argument but
         uses the list of collection provided to fill the choices of the
         drop-down list.
         """
@@ -546,7 +568,7 @@ class UpdateIssueForm(PagureForm):
 
 
 class AddPullRequestCommentForm(PagureForm):
-    """ Form to add a comment to a pull-request. """
+    """Form to add a comment to a pull-request."""
 
     commit = wtforms.HiddenField("commit identifier")
     filename = wtforms.HiddenField("file changed")
@@ -554,13 +576,13 @@ class AddPullRequestCommentForm(PagureForm):
     requestid = wtforms.HiddenField("requestid")
     tree_id = wtforms.HiddenField("treeid")
     comment = wtforms.TextAreaField(
-        'Comment<span class="error">*</span>',
+        "Comment",
         [wtforms.validators.DataRequired()],
     )
 
 
 class AddPullRequestFlagFormV1(PagureForm):
-    """ Form to add a flag to a pull-request or commit. """
+    """Form to add a flag to a pull-request or commit."""
 
     username = wtforms.StringField(
         "Username", [wtforms.validators.DataRequired()]
@@ -582,7 +604,7 @@ class AddPullRequestFlagFormV1(PagureForm):
 
 
 class AddPullRequestFlagForm(AddPullRequestFlagFormV1):
-    """ Form to add a flag to a pull-request or commit. """
+    """Form to add a flag to a pull-request or commit."""
 
     def __init__(self, *args, **kwargs):
         # we need to instantiate dynamically because the configuration
@@ -602,17 +624,17 @@ class AddPullRequestFlagForm(AddPullRequestFlagFormV1):
 
 
 class AddSSHKeyForm(PagureForm):
-    """ Form to add a SSH key to a user. """
+    """Form to add a SSH key to a user."""
 
     ssh_key = wtforms.StringField(
-        'SSH Key <span class="error">*</span>',
+        "SSH Key",
         [wtforms.validators.DataRequired()]
         # TODO: Add an ssh key validator?
     )
 
 
 class AddDeployKeyForm(AddSSHKeyForm):
-    """ Form to add a deploy key to a project. """
+    """Form to add a deploy key to a project."""
 
     pushaccess = wtforms.BooleanField(
         "Push access",
@@ -622,68 +644,68 @@ class AddDeployKeyForm(AddSSHKeyForm):
 
 
 class AddUserForm(PagureForm):
-    """ Form to add a user to a project. """
+    """Form to add a user to a project."""
 
     user = wtforms.StringField(
-        'Username <span class="error">*</span>',
+        "Username",
         [wtforms.validators.DataRequired()],
     )
     access = wtforms.StringField(
-        'Access Level <span class="error">*</span>',
+        "Access Level",
         [wtforms.validators.DataRequired()],
     )
     branches = wtforms.StringField(
-        'Git branches <span class="error">*</span>',
+        "Git branches",
         [wtforms.validators.Optional()],
     )
 
 
 class AddUserToGroupForm(PagureForm):
-    """ Form to add a user to a pagure group. """
+    """Form to add a user to a pagure group."""
 
     user = wtforms.StringField(
-        'Username <span class="error">*</span>',
+        "Username",
         [wtforms.validators.DataRequired()],
     )
 
 
 class AssignIssueForm(PagureForm):
-    """ Form to assign an user to an issue. """
+    """Form to assign an user to an issue."""
 
     assignee = wtforms.StringField(
-        'Assignee <span class="error">*</span>',
+        "Assignee",
         [wtforms.validators.Optional()],
     )
 
 
 class AddGroupForm(PagureForm):
-    """ Form to add a group to a project. """
+    """Form to add a group to a project."""
 
     group = wtforms.StringField(
-        'Group <span class="error">*</span>',
+        "Group",
         [
             wtforms.validators.DataRequired(),
             wtforms.validators.Regexp(STRICT_REGEX, flags=re.IGNORECASE),
         ],
     )
     access = wtforms.StringField(
-        'Access Level <span class="error">*</span>',
+        "Access Level",
         [wtforms.validators.DataRequired()],
     )
     branches = wtforms.StringField(
-        'Git branches <span class="error">*</span>',
+        "Git branches",
         [wtforms.validators.Optional()],
     )
 
 
 class ConfirmationForm(PagureForm):
-    """ Simple form used just for CSRF protection. """
+    """Simple form used just for CSRF protection."""
 
     pass
 
 
 class ModifyACLForm(PagureForm):
-    """ Form to change ACL of a user or a group to a project. """
+    """Form to change ACL of a user or a group to a project."""
 
     user_type = wtforms.SelectField(
         "User type",
@@ -691,7 +713,7 @@ class ModifyACLForm(PagureForm):
         choices=[("user", "User"), ("group", "Group")],
     )
     name = wtforms.StringField(
-        'User- or Groupname <span class="error">*</span>',
+        "User- or Groupname",
         [wtforms.validators.DataRequired()],
     )
     acl = wtforms.SelectField(
@@ -708,7 +730,7 @@ class ModifyACLForm(PagureForm):
 
 
 class UploadFileForm(PagureForm):
-    """ Form to upload a file. """
+    """Form to upload a file."""
 
     filestream = wtforms.FileField(
         "File", [wtforms.validators.DataRequired(), file_virus_validator]
@@ -716,7 +738,7 @@ class UploadFileForm(PagureForm):
 
 
 class UserEmailForm(PagureForm):
-    """ Form to edit the description of a project. """
+    """Form to edit the description of a project."""
 
     email = wtforms.StringField("email", [wtforms.validators.DataRequired()])
 
@@ -732,7 +754,7 @@ class UserEmailForm(PagureForm):
 
 
 class ProjectCommentForm(PagureForm):
-    """ Form to represent project. """
+    """Form to represent project."""
 
     objid = wtforms.StringField(
         "Ticket/Request id", [wtforms.validators.DataRequired()]
@@ -743,7 +765,7 @@ class ProjectCommentForm(PagureForm):
 
 
 class CommentForm(PagureForm):
-    """ Form to upload a file. """
+    """Form to upload a file."""
 
     comment = wtforms.FileField(
         "Comment", [wtforms.validators.DataRequired(), file_virus_validator]
@@ -751,7 +773,7 @@ class CommentForm(PagureForm):
 
 
 class EditGroupForm(PagureForm):
-    """ Form to ask for a password change. """
+    """Form to ask for a password change."""
 
     display_name = wtforms.StringField(
         "Group name to display",
@@ -770,10 +792,10 @@ class EditGroupForm(PagureForm):
 
 
 class NewGroupForm(EditGroupForm):
-    """ Form to ask for a password change. """
+    """Form to ask for a password change."""
 
     group_name = wtforms.StringField(
-        'Group name  <span class="error">*</span>',
+        "Group name",
         [
             wtforms.validators.DataRequired(),
             wtforms.validators.Length(max=255),
@@ -785,7 +807,7 @@ class NewGroupForm(EditGroupForm):
     )
 
     def __init__(self, *args, **kwargs):
-        """ Calls the default constructor with the normal argument but
+        """Calls the default constructor with the normal argument but
         uses the list of collection provided to fill the choices of the
         drop-down list.
         """
@@ -797,7 +819,7 @@ class NewGroupForm(EditGroupForm):
 
 
 class EditFileForm(PagureForm):
-    """ Form used to edit a file. """
+    """Form used to edit a file."""
 
     content = wtforms.TextAreaField("content", [wtforms.validators.Optional()])
     commit_title = wtforms.StringField(
@@ -812,7 +834,7 @@ class EditFileForm(PagureForm):
     branch = wtforms.StringField("Branch", [wtforms.validators.DataRequired()])
 
     def __init__(self, *args, **kwargs):
-        """ Calls the default constructor with the normal argument but
+        """Calls the default constructor with the normal argument but
         uses the list of collection provided to fill the choices of the
         drop-down list.
         """
@@ -831,7 +853,7 @@ class DefaultBranchForm(PagureForm):
     )
 
     def __init__(self, *args, **kwargs):
-        """ Calls the default constructor with the normal argument but
+        """Calls the default constructor with the normal argument but
         uses the list of collection provided to fill the choices of the
         drop-down list.
         """
@@ -850,7 +872,7 @@ class DefaultPriorityForm(PagureForm):
     )
 
     def __init__(self, *args, **kwargs):
-        """ Calls the default constructor with the normal argument but
+        """Calls the default constructor with the normal argument but
         uses the list of collection provided to fill the choices of the
         drop-down list.
         """
@@ -862,17 +884,16 @@ class DefaultPriorityForm(PagureForm):
 
 
 class EditCommentForm(PagureForm):
-    """ Form to verify that comment is not empty
-    """
+    """Form to verify that comment is not empty"""
 
     update_comment = wtforms.TextAreaField(
-        'Comment<span class="error">*</span>',
+        "Comment ",
         [wtforms.validators.DataRequired()],
     )
 
 
 class ForkRepoForm(PagureForm):
-    """ Form to fork a project in the API. """
+    """Form to fork a project in the API."""
 
     repo = wtforms.StringField(
         "The project name", [wtforms.validators.DataRequired()]
@@ -886,32 +907,30 @@ class ForkRepoForm(PagureForm):
 
 
 class AddReportForm(PagureForm):
-    """ Form to verify that comment is not empty
-    """
+    """Form to verify that comment is not empty"""
 
     report_name = wtforms.TextAreaField(
-        'Report name<span class="error">*</span>',
+        "Report name",
         [wtforms.validators.DataRequired()],
     )
 
 
 class PublicNotificationForm(PagureForm):
-    """ Form to verify that comment is not empty
-    """
+    """Form to verify that comment is not empty"""
 
     issue_notifs = wtforms.TextAreaField(
-        'Public issue notification<span class="error">*</span>',
+        "Public issue notification",
         [wtforms.validators.optional(), MultipleEmail()],
     )
 
     pr_notifs = wtforms.TextAreaField(
-        'Public PR notification<span class="error">*</span>',
+        "Public PR notification",
         [wtforms.validators.optional(), MultipleEmail()],
     )
 
 
 class SubscribtionForm(PagureForm):
-    """ Form to subscribe to or unsubscribe from an issue or a PR. """
+    """Form to subscribe to or unsubscribe from an issue or a PR."""
 
     status = wtforms.BooleanField(
         "Subscription status",
@@ -945,15 +964,15 @@ class TriggerCIPRForm(PagureForm):
         self.comment.choices = choices
 
     comment = wtforms.SelectField(
-        "comment", [wtforms.validators.Required()], choices=[]
+        "comment", [wtforms.validators.DataRequired()], choices=[]
     )
 
 
 class AddGitTagForm(PagureForm):
-    """ Form to create a new git tag. """
+    """Form to create a new git tag."""
 
     tagname = wtforms.StringField(
-        'Name of the tag<span class="error">*</span>',
+        "Name of the tag",
         [wtforms.validators.DataRequired()],
     )
     commit_hash = wtforms.StringField(
diff --git a/pagure/hooks/__init__.py b/pagure/hooks/__init__.py
index a848afa..d49fcda 100644
--- a/pagure/hooks/__init__.py
+++ b/pagure/hooks/__init__.py
@@ -8,26 +8,26 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
+import os
 import subprocess
 import sys
 import traceback
-import os
 
 import six
 import wtforms
 
+import pagure.lib.git
+import pagure.lib.query
 from pagure.config import config as pagure_config
 from pagure.exceptions import FileNotFoundException
-import pagure.lib.query
-import pagure.lib.git
 from pagure.lib.git_auth import get_git_auth_helper
 from pagure.lib.plugins import get_enabled_plugins
 
 
 class RequiredIf(wtforms.validators.DataRequired):
-    """ Wtforms validator setting a field as required if another field
+    """Wtforms validator setting a field as required if another field
     has a value.
     """
 
@@ -64,7 +64,7 @@ class BaseRunner(object):
     def runhook(
         cls, session, username, hooktype, project, repotype, repodir, changes
     ):
-        """ Run a specific hook on a project.
+        """Run a specific hook on a project.
 
         By default, this calls out to the pre_receive, update or post_receive
         functions as appropriate.
@@ -115,7 +115,7 @@ class BaseRunner(object):
 
     @staticmethod
     def pre_receive(session, username, project, repotype, repodir, changes):
-        """ Run the pre-receive tasks of a hook.
+        """Run the pre-receive tasks of a hook.
 
         For args, see BaseRunner.runhook.
         """
@@ -123,7 +123,7 @@ class BaseRunner(object):
 
     @staticmethod
     def update(session, username, project, repotype, repodir, changes):
-        """ Run the update tasks of a hook.
+        """Run the update tasks of a hook.
 
         For args, see BaseRunner.runhook.
         Note that the "changes" list has exactly one element.
@@ -132,7 +132,7 @@ class BaseRunner(object):
 
     @staticmethod
     def post_receive(session, username, project, repotype, repodir, changes):
-        """ Run the post-receive tasks of a hook.
+        """Run the post-receive tasks of a hook.
 
         For args, see BaseRunner.runhook.
         """
@@ -140,7 +140,7 @@ class BaseRunner(object):
 
 
 class BaseHook(object):
-    """ Base class for pagure's hooks. """
+    """Base class for pagure's hooks."""
 
     name = None
     form = None
@@ -154,7 +154,7 @@ class BaseHook(object):
 
     @classmethod
     def set_up(cls, project):
-        """ Install the generic post-receive hook that allow us to call
+        """Install the generic post-receive hook that allow us to call
         multiple post-receive hooks as set per plugin.
         """
         if project.is_on_repospanner:
@@ -186,7 +186,7 @@ class BaseHook(object):
 
     @classmethod
     def base_install(cls, repopaths, dbobj, hook_name, filein):
-        """ Method called to install the hook for a project.
+        """Method called to install the hook for a project.
 
         :arg project: a ``pagure.model.Project`` object to which the hook
             should be installed
@@ -222,7 +222,7 @@ class BaseHook(object):
 
     @classmethod
     def base_remove(cls, repopaths, hook_name):
-        """ Method called to remove the hook of a project.
+        """Method called to remove the hook of a project.
 
         :arg project: a ``pagure.model.Project`` object to which the hook
             should be installed
@@ -240,7 +240,7 @@ class BaseHook(object):
 
     @classmethod
     def install(cls, *args):
-        """ In sub-classess, this can be used for installation of the hook.
+        """In sub-classess, this can be used for installation of the hook.
 
         However, this is not required anymore for hooks with a Runner.
         This class is here as backwards compatibility.
@@ -252,7 +252,7 @@ class BaseHook(object):
 
     @classmethod
     def remove(cls, *args):
-        """ In sub-classess, this can be used for removal of the hook.
+        """In sub-classess, this can be used for removal of the hook.
 
         However, this is not required anymore for hooks with a Runner.
         This class is here as backwards compatibility.
@@ -264,7 +264,7 @@ class BaseHook(object):
 
     @classmethod
     def is_enabled_for(cls, project):
-        """ Determine if this hook should be run for given project.
+        """Determine if this hook should be run for given project.
 
         On some Pagure instances, some hooks should be run on all projects
         that fulfill certain criteria. It is therefore not necessary to keep
@@ -294,7 +294,7 @@ def run_project_hooks(
     is_internal,
     pull_request,
 ):
-    """ Function to run the hooks on a project
+    """Function to run the hooks on a project
 
     This will first call all the plugins with a Runner on the project,
     and afterwards, for a non-repoSpanner repo, run all hooks/<hooktype>.*
@@ -470,7 +470,7 @@ def run_project_hooks(
 
 
 def extract_changes(from_stdin):
-    """ Extracts a changes dict from either stdin or argv
+    """Extracts a changes dict from either stdin or argv
 
     Args:
         from_stdin (bool): Whether to use stdin. If false, uses argv
@@ -491,12 +491,15 @@ def extract_changes(from_stdin):
 
 
 def run_hook_file(hooktype):
-    """ Runs a specific hook by grabbing the changes and running functions.
+    """Runs a specific hook by grabbing the changes and running functions.
 
     Args:
         hooktype (string): The name of the hook to run: pre-receive, update
             or post-receive
     """
+    if pagure_config.get("NOGITHOOKS") or False:
+        return
+
     if hooktype not in ("pre-receive", "update", "post-receive"):
         raise ValueError("Hook type %s not valid" % hooktype)
     changes = extract_changes(from_stdin=hooktype != "update")
diff --git a/pagure/hooks/default.py b/pagure/hooks/default.py
index 9fae328..0a0a3d7 100644
--- a/pagure/hooks/default.py
+++ b/pagure/hooks/default.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, print_function, absolute_import
+from __future__ import absolute_import, print_function, unicode_literals
 
 import logging
 
@@ -23,7 +23,6 @@ import pagure.lib.tasks_services
 import pagure.utils
 from pagure.hooks import BaseHook, BaseRunner
 
-
 _config = pagure.config.reload_config()
 _log = logging.getLogger(__name__)
 
@@ -32,7 +31,7 @@ FEDMSG_INIT = False
 
 
 def send_fedmsg_notifications(project, topic, msg):
-    """ If the user or admin asked for fedmsg notifications on commit, this will
+    """If the user or admin asked for fedmsg notifications on commit, this will
     do it.
     """
 
@@ -75,7 +74,7 @@ def send_fedmsg_notifications(project, topic, msg):
 
 
 def send_stomp_notifications(project, topic, msg):
-    """ If the user or admin asked for stomp notifications on commit, this will
+    """If the user or admin asked for stomp notifications on commit, this will
     do it.
     """
     always_stomp = _config.get("ALWAYS_STOMP_ON_COMMITS") or None
@@ -89,7 +88,7 @@ def send_stomp_notifications(project, topic, msg):
 
 
 def send_mqtt_notifications(project, topic, msg):
-    """ If the user or admin asked for mqtt notifications on commit, this will
+    """If the user or admin asked for mqtt notifications on commit, this will
     do it.
     """
     always_mqtt = _config.get("ALWAYS_MQTT_ON_COMMITS") or None
@@ -103,7 +102,7 @@ def send_mqtt_notifications(project, topic, msg):
 
 
 def send_webhook_notifications(project, topic, msg):
-    """ If the user asked for webhook notifications on commit, this will
+    """If the user asked for webhook notifications on commit, this will
     do it.
     """
     if project.settings.get("Web-hooks"):
@@ -125,15 +124,18 @@ def send_webhook_notifications(project, topic, msg):
 def send_action_notification(
     session, subject, action, project, repodir, user, refname, rev
 ):
-    """ Send out-going notifications about the branch/tag.
-    """
+    """Send out-going notifications about the branch/tag."""
     email = pagure.lib.git.get_author_email(rev, repodir)
     name = pagure.lib.git.get_author(rev, repodir)
-    author = pagure.lib.query.search_user(session, email=email)
-    if author:
+    author = pagure.lib.query.search_user(session, email=email) or name
+    if not isinstance(author, six.string_types):
         author = author.to_json(public=True)
     else:
-        author = name
+        author = {
+            "fullname": author,
+            "name": None,
+            "url_path": None,
+        }
 
     topic = "git.%s.%s" % (subject, action)
     msg = dict(
@@ -163,7 +165,7 @@ def send_action_notification(
 def send_notifications(
     session, project, repodir, user, refname, revs, forced, oldrev
 ):
-    """ Send out-going notifications about the commits that have just been
+    """Send out-going notifications about the commits that have just been
     pushed.
     """
 
@@ -178,6 +180,12 @@ def send_notifications(
     for author in auths:
         if not isinstance(author, six.string_types):
             author = author.to_json(public=True)
+        else:
+            author = {
+                "fullname": author,
+                "name": None,
+                "url_path": None,
+            }
         authors.append(author)
 
     if revs:
@@ -223,16 +231,18 @@ def send_notifications(
             )
 
 
-def inform_pull_request_urls(
-    session, project, commits, refname, default_branch
-):
-    """ Inform the user about the URLs to open a new pull-request or visit
+def inform_pull_request_urls(session, project, commits, refname, username):
+    """Inform the user about the URLs to open a new pull-request or visit
     the existing one.
     """
     target_repo = project
     if project.is_fork:
         target_repo = project.parent
 
+    taget_repo_obj = pygit2.Repository(target_repo.repopath("main"))
+    if not taget_repo_obj.is_empty and not taget_repo_obj.head_is_unborn:
+        default_branch = taget_repo_obj.head.shorthand
+
     pr_uids = []
 
     if (
@@ -260,7 +270,9 @@ def inform_pull_request_urls(
         seen = len(prs) != 0
         for pr in prs:
             # Refresh the PR in the db and everywhere else where needed
-            pagure.lib.tasks.update_pull_request.delay(pr.uid)
+            pagure.lib.tasks.update_pull_request.delay(
+                pr.uid, username=username
+            )
 
             # Link tickets with pull-requests if the commit mentions it
             pagure.lib.tasks.link_pr_to_ticket.delay(pr.uid)
@@ -291,11 +303,11 @@ def inform_pull_request_urls(
 
 
 class DefaultRunner(BaseRunner):
-    """ Runner for the default hook."""
+    """Runner for the default hook."""
 
     @staticmethod
     def post_receive(session, username, project, repotype, repodir, changes):
-        """ Run the default post-receive hook.
+        """Run the default post-receive hook.
 
         For args, see BaseRunner.runhook.
         """
@@ -429,7 +441,11 @@ class DefaultRunner(BaseRunner):
             # open a new pr or review the existing one
             pr_uids.extend(
                 inform_pull_request_urls(
-                    session, project, commits, refname, default_branch
+                    session,
+                    project,
+                    commits,
+                    refname,
+                    username,
                 )
             )
 
@@ -459,7 +475,7 @@ class DefaultRunner(BaseRunner):
 
 
 class Default(BaseHook):
-    """ Default hooks. """
+    """Default hooks."""
 
     name = "default"
     description = (
diff --git a/pagure/hooks/fedmsg_hook.py b/pagure/hooks/fedmsg_hook.py
index 5d5a7f8..8454804 100644
--- a/pagure/hooks/fedmsg_hook.py
+++ b/pagure/hooks/fedmsg_hook.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import sqlalchemy as sa
 import wtforms
@@ -17,15 +17,15 @@ try:
     from flask_wtf import FlaskForm
 except ImportError:
     from flask_wtf import Form as FlaskForm
-from sqlalchemy.orm import relation
-from sqlalchemy.orm import backref
+
+from sqlalchemy.orm import backref, relation
 
 from pagure.hooks import BaseHook, BaseRunner
 from pagure.lib.model import BASE, Project
 
 
 class FedmsgTable(BASE):
-    """ Stores information about the fedmsg hook deployed on a project.
+    """Stores information about the fedmsg hook deployed on a project.
 
     Table -- hook_fedmsg
     """
@@ -56,7 +56,7 @@ class FedmsgTable(BASE):
 
 
 class FedmsgRunner(BaseRunner):
-    """ Runner for the fedmsg hook, it does nothing as all the magic is
+    """Runner for the fedmsg hook, it does nothing as all the magic is
     part of the default hook/runner.
     """
 
@@ -64,7 +64,7 @@ class FedmsgRunner(BaseRunner):
 
 
 class FedmsgForm(FlaskForm):
-    """ Form to configure the fedmsg hook. """
+    """Form to configure the fedmsg hook."""
 
     active = wtforms.BooleanField("Active", [wtforms.validators.Optional()])
 
@@ -77,12 +77,12 @@ It is different from the fedmsg setting present in the project options block
 which publishes notifications about events happening in the project via
 pagure's (web) user interface, for example: new tickets, new comments, new
 pull-requests and so on.
-This hook on the other only acts on commits.
+This hook on the other hand only acts on commits.
 """
 
 
 class Fedmsg(BaseHook):
-    """ Fedmsg hooks. """
+    """Fedmsg hooks."""
 
     name = "Fedmsg"
     description = DESCRIPTION
@@ -94,7 +94,7 @@ class Fedmsg(BaseHook):
 
     @classmethod
     def install(cls, project, dbobj):
-        """ Method called to install the hook for a project.
+        """Method called to install the hook for a project.
 
         :arg project: a ``pagure.model.Project`` object to which the hook
             should be installed
@@ -109,7 +109,7 @@ class Fedmsg(BaseHook):
 
     @classmethod
     def remove(cls, project):
-        """ Method called to remove the hook of a project.
+        """Method called to remove the hook of a project.
 
         :arg project: a ``pagure.model.Project`` object to which the hook
             should be installed
diff --git a/pagure/hooks/files/git_multimail_upstream.py b/pagure/hooks/files/git_multimail_upstream.py
index 0780563..7983052 100755
--- a/pagure/hooks/files/git_multimail_upstream.py
+++ b/pagure/hooks/files/git_multimail_upstream.py
@@ -2920,18 +2920,18 @@ class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin):
 class FilterLinesEnvironmentMixin(Environment):
     """Handle encoding and maximum line length of body lines.
 
-        email_max_line_length (int or None)
+    email_max_line_length (int or None)
 
-            The maximum length of any single line in the email body.
-            Longer lines are truncated at that length with ' [...]'
-            appended.
+        The maximum length of any single line in the email body.
+        Longer lines are truncated at that length with ' [...]'
+        appended.
 
-        strict_utf8 (bool)
+    strict_utf8 (bool)
 
-            If this field is set to True, then the email body text is
-            expected to be UTF-8.  Any invalid characters are
-            converted to U+FFFD, the Unicode replacement character
-            (encoded as UTF-8, of course).
+        If this field is set to True, then the email body text is
+        expected to be UTF-8.  Any invalid characters are
+        converted to U+FFFD, the Unicode replacement character
+        (encoded as UTF-8, of course).
 
     """
 
@@ -3623,8 +3623,7 @@ class Push(object):
 
     @property
     def _other_ref_sha1s(self):
-        """The GitObjects referred to by references unaffected by this push.
-        """
+        """The GitObjects referred to by references unaffected by this push."""
         if self.__other_ref_sha1s is None:
             # The refnames being changed by this push:
             updated_refs = set(change.refname for change in self.changes)
@@ -3708,7 +3707,7 @@ class Push(object):
         new_or_old is either the string 'new' or the string 'old'.  If
         'new', the commits to be excluded are those that were in the
         repository before the push.  If 'old', the commits to be
-        excluded are those that are currently in the repository.  """
+        excluded are those that are currently in the repository."""
 
         old_or_new = {"old": "new", "new": "old"}[new_or_old]
         excl_revs = self._other_ref_sha1s.union(
diff --git a/pagure/hooks/irc.py b/pagure/hooks/irc.py
index 2725b31..87155ac 100644
--- a/pagure/hooks/irc.py
+++ b/pagure/hooks/irc.py
@@ -8,18 +8,18 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
-import sqlalchemy as sa
 import pygit2
+import sqlalchemy as sa
 import wtforms
 
 try:
     from flask_wtf import FlaskForm
 except ImportError:
     from flask_wtf import Form as FlaskForm
-from sqlalchemy.orm import relation
-from sqlalchemy.orm import backref
+
+from sqlalchemy.orm import backref, relation
 
 from pagure.hooks import BaseHook, RequiredIf
 from pagure.lib.model import BASE, Project
@@ -27,7 +27,7 @@ from pagure.utils import get_repo_path
 
 
 class IrcTable(BASE):
-    """ Stores information about the irc hook deployed on a project.
+    """Stores information about the irc hook deployed on a project.
 
     Table -- hook_irc
     """
@@ -65,7 +65,7 @@ class IrcTable(BASE):
 
 
 class IrcForm(FlaskForm):
-    """ Form to configure the irc hook. """
+    """Form to configure the irc hook."""
 
     server = wtforms.StringField(
         'Server <span class="error">*</span>', [RequiredIf("active")]
@@ -89,7 +89,7 @@ class IrcForm(FlaskForm):
 
 
 class Hook(BaseHook):
-    """ IRC hooks. """
+    """IRC hooks."""
 
     name = "IRC"
     description = (
@@ -112,7 +112,7 @@ class Hook(BaseHook):
 
     @classmethod
     def install(cls, project, dbobj):
-        """ Method called to install the hook for a project.
+        """Method called to install the hook for a project.
 
         :arg project: a ``pagure.model.Project`` object to which the hook
             should be installed
@@ -130,7 +130,7 @@ class Hook(BaseHook):
 
     @classmethod
     def remove(cls, project):
-        """ Method called to remove the hook of a project.
+        """Method called to remove the hook of a project.
 
         :arg project: a ``pagure.model.Project`` object to which the hook
             should be installed
diff --git a/pagure/hooks/mail.py b/pagure/hooks/mail.py
index 989990b..64bcf42 100644
--- a/pagure/hooks/mail.py
+++ b/pagure/hooks/mail.py
@@ -8,20 +8,21 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
-import sqlalchemy as sa
 import os
-import pygit2
 import subprocess
+
+import pygit2
+import sqlalchemy as sa
 import wtforms
 
 try:
     from flask_wtf import FlaskForm
 except ImportError:
     from flask_wtf import Form as FlaskForm
-from sqlalchemy.orm import relation
-from sqlalchemy.orm import backref
+
+from sqlalchemy.orm import backref, relation
 
 from pagure.config import config as pagure_config
 from pagure.hooks import BaseHook, BaseRunner, RequiredIf
@@ -29,7 +30,7 @@ from pagure.lib.model import BASE, Project
 
 
 class MailTable(BASE):
-    """ Stores information about the mail hook deployed on a project.
+    """Stores information about the mail hook deployed on a project.
 
     Table -- hook_mail
     """
@@ -61,7 +62,7 @@ class MailTable(BASE):
 
 
 class MailForm(FlaskForm):
-    """ Form to configure the mail hook. """
+    """Form to configure the mail hook."""
 
     mail_to = wtforms.StringField("Mail to", [RequiredIf("active")])
     active = wtforms.BooleanField("Active", [wtforms.validators.Optional()])
@@ -70,7 +71,7 @@ class MailForm(FlaskForm):
 class MailRunner(BaseRunner):
     @staticmethod
     def post_receive(session, username, project, repotype, repodir, changes):
-        """ Run the multimail post-receive hook.
+        """Run the multimail post-receive hook.
 
         For args, see BaseRunner.runhook.
         """
@@ -128,7 +129,7 @@ class MailRunner(BaseRunner):
         proc = subprocess.Popen(
             [hook_file], cwd=repodir, stdin=subprocess.PIPE
         )
-        proc.communicate(stdin)
+        proc.communicate(stdin.encode())
         ecode = proc.wait()
         if ecode != 0:
             print("git_multimail failed")
@@ -136,7 +137,7 @@ class MailRunner(BaseRunner):
 
 
 class Mail(BaseHook):
-    """ Mail hooks. """
+    """Mail hooks."""
 
     name = "Mail"
     description = (
diff --git a/pagure/hooks/mirror_hook.py b/pagure/hooks/mirror_hook.py
index 1a577b0..d4dffb1 100644
--- a/pagure/hooks/mirror_hook.py
+++ b/pagure/hooks/mirror_hook.py
@@ -7,7 +7,7 @@
    Pierre-Yves Chibon <pingou@pingoured.fr>
 
 """
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import sqlalchemy as sa
 import wtforms
@@ -16,8 +16,8 @@ try:
     from flask_wtf import FlaskForm
 except ImportError:
     from flask_wtf import Form as FlaskForm
-from sqlalchemy.orm import relation
-from sqlalchemy.orm import backref
+
+from sqlalchemy.orm import backref, relation
 
 import pagure.config
 import pagure.lib.tasks_mirror
@@ -25,12 +25,11 @@ from pagure.hooks import BaseHook, BaseRunner, RequiredIf
 from pagure.lib.model import BASE, Project
 from pagure.utils import get_repo_path, ssh_urlpattern
 
-
 _config = pagure.config.reload_config()
 
 
 class MirrorTable(BASE):
-    """ Stores information about the mirroring hook deployed on a project.
+    """Stores information about the mirroring hook deployed on a project.
 
     Table -- mirror_pagure
     """
@@ -65,11 +64,11 @@ class MirrorTable(BASE):
 
 
 class MirrorRunner(BaseRunner):
-    """ Runner for the mirror hook. """
+    """Runner for the mirror hook."""
 
     @staticmethod
     def post_receive(session, username, project, repotype, repodir, changes):
-        """ Run the default post-receive hook.
+        """Run the default post-receive hook.
 
         For args, see BaseRunner.runhook.
         """
@@ -102,7 +101,7 @@ class CustomRegexp(wtforms.validators.Regexp):
 
 
 class MirrorForm(FlaskForm):
-    """ Form to configure the mirror hook. """
+    """Form to configure the mirror hook."""
 
     active = wtforms.BooleanField("Active", [wtforms.validators.Optional()])
 
@@ -137,7 +136,7 @@ Finally the log of the last sync at the bottom is meant.
 
 
 class MirrorHook(BaseHook):
-    """ Mirror hook. """
+    """Mirror hook."""
 
     name = "Mirroring"
     description = DESCRIPTION
@@ -150,7 +149,7 @@ class MirrorHook(BaseHook):
 
     @classmethod
     def install(cls, project, dbobj):
-        """ Method called to install the hook for a project.
+        """Method called to install the hook for a project.
 
         :arg project: a ``pagure.model.Project`` object to which the hook
             should be installed
@@ -167,7 +166,7 @@ class MirrorHook(BaseHook):
 
     @classmethod
     def remove(cls, project):
-        """ Method called to remove the hook of a project.
+        """Method called to remove the hook of a project.
 
         :arg project: a ``pagure.model.Project`` object to which the hook
             should be installed
diff --git a/pagure/hooks/pagure_ci.py b/pagure/hooks/pagure_ci.py
index 795a050..4cce6dd 100644
--- a/pagure/hooks/pagure_ci.py
+++ b/pagure/hooks/pagure_ci.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import flask
 import sqlalchemy as sa
@@ -18,8 +18,8 @@ try:
     from flask_wtf import FlaskForm
 except ImportError:
     from flask_wtf import Form as FlaskForm
-from sqlalchemy.orm import relation
-from sqlalchemy.orm import backref
+
+from sqlalchemy.orm import backref, relation
 
 import pagure.lib.login
 from pagure.hooks import BaseHook, BaseRunner, RequiredIf
@@ -27,7 +27,7 @@ from pagure.lib.model import BASE, Project
 
 
 class PagureCITable(BASE):
-    """ Stores information about the CI linked to on a project.
+    """Stores information about the CI linked to on a project.
 
     Table -- hook_pagure_ci
     """
@@ -65,7 +65,7 @@ class PagureCITable(BASE):
 
 
 class PagureCIRunner(BaseRunner):
-    """ Runner for the pagure-ci hook, it does nothing as the magic is part
+    """Runner for the pagure-ci hook, it does nothing as the magic is part
     of the CI system itself (to see if there was a commit made and build if
     so).
     """
@@ -100,7 +100,7 @@ activation.
 
 
 class PagureCiForm(FlaskForm):
-    """ Form to configure the CI hook. """
+    """Form to configure the CI hook."""
 
     ci_type = wtforms.SelectField(
         "Type of CI service",
@@ -151,7 +151,7 @@ class PagureCiForm(FlaskForm):
     )
 
     def __init__(self, *args, **kwargs):
-        """ Calls the default constructor with the normal argument but
+        """Calls the default constructor with the normal argument but
         uses the list of collection provided to fill the choices of the
         drop-down list.
         """
@@ -162,7 +162,7 @@ class PagureCiForm(FlaskForm):
 
 
 class PagureCi(BaseHook):
-    """ Continuous Integration (CI) hooks. """
+    """Continuous Integration (CI) hooks."""
 
     name = "Pagure CI"
     description = (
@@ -179,14 +179,14 @@ class PagureCi(BaseHook):
 
     @classmethod
     def set_up(cls, project):
-        """ Install the generic post-receive hook that allow us to call
+        """Install the generic post-receive hook that allow us to call
         multiple post-receive hooks as set per plugin.
         """
         pass
 
     @classmethod
     def install(cls, project, dbobj):
-        """ Method called to install the hook for a project.
+        """Method called to install the hook for a project.
 
         :arg project: a ``pagure.model.Project`` object to which the hook
             should be installed
@@ -198,7 +198,7 @@ class PagureCi(BaseHook):
 
     @classmethod
     def remove(cls, project):
-        """ Method called to remove the hook of a project.
+        """Method called to remove the hook of a project.
 
         :arg project: a ``pagure.model.Project`` object to which the hook
             should be installed
diff --git a/pagure/hooks/pagure_force_commit.py b/pagure/hooks/pagure_force_commit.py
index ac69004..47d4bcf 100644
--- a/pagure/hooks/pagure_force_commit.py
+++ b/pagure/hooks/pagure_force_commit.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import sqlalchemy as sa
 import wtforms
@@ -17,8 +17,8 @@ try:
     from flask_wtf import FlaskForm
 except ImportError:
     from flask_wtf import Form as FlaskForm
-from sqlalchemy.orm import relation
-from sqlalchemy.orm import backref
+
+from sqlalchemy.orm import backref, relation
 
 import pagure.lib.git
 from pagure.hooks import BaseHook, BaseRunner, RequiredIf
@@ -26,7 +26,7 @@ from pagure.lib.model import BASE, Project
 
 
 class PagureForceCommitTable(BASE):
-    """ Stores information about the pagure hook deployed on a project.
+    """Stores information about the pagure hook deployed on a project.
 
     Table -- hook_pagure_force_commit
     """
@@ -60,11 +60,11 @@ class PagureForceCommitTable(BASE):
 
 
 class PagureForceCommitRunner(BaseRunner):
-    """ Runner for the hook blocking force push. """
+    """Runner for the hook blocking force push."""
 
     @staticmethod
     def pre_receive(session, username, project, repotype, repodir, changes):
-        """ Run the pre-receive tasks of a hook.
+        """Run the pre-receive tasks of a hook.
 
         For args, see BaseRunner.runhook.
         """
@@ -93,7 +93,7 @@ class PagureForceCommitRunner(BaseRunner):
 
 
 class PagureForceCommitForm(FlaskForm):
-    """ Form to configure the pagure hook. """
+    """Form to configure the pagure hook."""
 
     branches = wtforms.StringField("Branches", [RequiredIf("active")])
 
@@ -101,7 +101,7 @@ class PagureForceCommitForm(FlaskForm):
 
 
 class PagureForceCommitHook(BaseHook):
-    """ PagurPagureForceCommit hook. """
+    """PagurPagureForceCommit hook."""
 
     name = "Block non fast-forward pushes"
     description = (
diff --git a/pagure/hooks/pagure_hook.py b/pagure/hooks/pagure_hook.py
index eef463f..7e9f3f2 100644
--- a/pagure/hooks/pagure_hook.py
+++ b/pagure/hooks/pagure_hook.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import logging
 
@@ -20,23 +20,22 @@ try:
     from flask_wtf import FlaskForm
 except ImportError:
     from flask_wtf import Form as FlaskForm
+
 from sqlalchemy.exc import SQLAlchemyError
-from sqlalchemy.orm import relation
-from sqlalchemy.orm import backref
+from sqlalchemy.orm import backref, relation
 
 import pagure.config
-import pagure.lib.query
 import pagure.lib.git
+import pagure.lib.query
 from pagure.hooks import BaseHook, BaseRunner
 from pagure.lib.model import BASE, Project
 
-
 _log = logging.getLogger(__name__)
 pagure_config = pagure.config.reload_config()
 
 
 class PagureTable(BASE):
-    """ Stores information about the pagure hook deployed on a project.
+    """Stores information about the pagure hook deployed on a project.
 
     Table -- hook_pagure
     """
@@ -119,7 +118,7 @@ def generate_revision_change_log(
 
 
 def relates_commit(session, username, commitid, issue, app_url=None):
-    """ Add a comment to an issue that this commit relates to it. """
+    """Add a comment to an issue that this commit relates to it."""
 
     url = "../%s" % commitid[:8]
     if app_url:
@@ -148,8 +147,8 @@ def relates_commit(session, username, commitid, issue, app_url=None):
 
 
 def fixes_relation(session, username, commitid, relation, app_url=None):
-    """ Add a comment to an issue or PR that this commit fixes it and update
-    the status if the commit is in the master branch. """
+    """Add a comment to an issue or PR that this commit fixes it and update
+    the status if the commit is in the master branch."""
 
     url = "../c/%s" % commitid[:8]
     if app_url:
@@ -212,11 +211,11 @@ def fixes_relation(session, username, commitid, relation, app_url=None):
 
 
 class PagureRunner(BaseRunner):
-    """ Runner for the pagure's specific git hook. """
+    """Runner for the pagure's specific git hook."""
 
     @staticmethod
     def post_receive(session, username, project, repotype, repodir, changes):
-        """ Run the default post-receive hook.
+        """Run the default post-receive hook.
 
         For args, see BaseRunner.runhook.
         """
@@ -259,7 +258,7 @@ class PagureRunner(BaseRunner):
 
 
 class PagureForm(FlaskForm):
-    """ Form to configure the pagure hook. """
+    """Form to configure the pagure hook."""
 
     active = wtforms.BooleanField("Active", [wtforms.validators.Optional()])
 
@@ -300,7 +299,7 @@ number.
 
 
 class PagureHook(BaseHook):
-    """ Pagure hook. """
+    """Pagure hook."""
 
     name = "Pagure"
     description = DESCRIPTION
diff --git a/pagure/hooks/pagure_no_new_branches.py b/pagure/hooks/pagure_no_new_branches.py
index 717c8ef..3c02754 100644
--- a/pagure/hooks/pagure_no_new_branches.py
+++ b/pagure/hooks/pagure_no_new_branches.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import sqlalchemy as sa
 import wtforms
@@ -17,15 +17,15 @@ try:
     from flask_wtf import FlaskForm
 except ImportError:
     from flask_wtf import Form as FlaskForm
-from sqlalchemy.orm import relation
-from sqlalchemy.orm import backref
+
+from sqlalchemy.orm import backref, relation
 
 from pagure.hooks import BaseHook, BaseRunner
 from pagure.lib.model import BASE, Project
 
 
 class PagureNoNewBranchesTable(BASE):
-    """ Stores information about the pagure hook deployed on a project.
+    """Stores information about the pagure hook deployed on a project.
 
     Table -- hook_pagure_no_new_branches
     """
@@ -56,11 +56,11 @@ class PagureNoNewBranchesTable(BASE):
 
 
 class PagureNoNewBranchRunner(BaseRunner):
-    """ Runner for the hook blocking new branches from being created. """
+    """Runner for the hook blocking new branches from being created."""
 
     @staticmethod
     def pre_receive(session, username, project, repotype, repodir, changes):
-        """ Run the pre-receive tasks of a hook.
+        """Run the pre-receive tasks of a hook.
 
         For args, see BaseRunner.runhook.
         """
@@ -84,13 +84,13 @@ class PagureNoNewBranchRunner(BaseRunner):
 
 
 class PagureNoNewBranchesForm(FlaskForm):
-    """ Form to configure the pagure hook. """
+    """Form to configure the pagure hook."""
 
     active = wtforms.BooleanField("Active", [wtforms.validators.Optional()])
 
 
 class PagureNoNewBranchesHook(BaseHook):
-    """ PagureNoNewBranches hook. """
+    """PagureNoNewBranches hook."""
 
     name = "Prevent creating new branches by git push"
     description = "This hook prevents creating new branches by git push."
diff --git a/pagure/hooks/pagure_request_hook.py b/pagure/hooks/pagure_request_hook.py
index 776601a..c3533a4 100644
--- a/pagure/hooks/pagure_request_hook.py
+++ b/pagure/hooks/pagure_request_hook.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import sqlalchemy as sa
 import wtforms
@@ -17,8 +17,8 @@ try:
     from flask_wtf import FlaskForm
 except ImportError:
     from flask_wtf import Form as FlaskForm
-from sqlalchemy.orm import relation
-from sqlalchemy.orm import backref
+
+from sqlalchemy.orm import backref, relation
 
 import pagure.lib.git
 import pagure.lib.tasks_services
@@ -27,7 +27,7 @@ from pagure.lib.model import BASE, Project
 
 
 class PagureRequestsTable(BASE):
-    """ Stores information about the pagure requests hook deployed on a
+    """Stores information about the pagure requests hook deployed on a
     project.
 
     Table -- hook_pagure_requests
@@ -59,13 +59,13 @@ class PagureRequestsTable(BASE):
 
 
 class PagureRequestRunner(BaseRunner):
-    """ Runner for the hook updating the db about requests on push to the
+    """Runner for the hook updating the db about requests on push to the
     git repo containing the meta-data about pull-requests.
     """
 
     @staticmethod
     def post_receive(session, username, project, repotype, repodir, changes):
-        """ Run the default post-receive hook.
+        """Run the default post-receive hook.
 
         For args, see BaseRunner.runhook.
         """
@@ -108,13 +108,13 @@ class PagureRequestRunner(BaseRunner):
 
 
 class PagureRequestsForm(FlaskForm):
-    """ Form to configure the pagure hook. """
+    """Form to configure the pagure hook."""
 
     active = wtforms.BooleanField("Active", [wtforms.validators.Optional()])
 
 
 class PagureRequestHook(BaseHook):
-    """ Pagure request hook. """
+    """Pagure request hook."""
 
     name = "Pagure requests"
     description = (
diff --git a/pagure/hooks/pagure_ticket_hook.py b/pagure/hooks/pagure_ticket_hook.py
index 9bd6672..085b75f 100644
--- a/pagure/hooks/pagure_ticket_hook.py
+++ b/pagure/hooks/pagure_ticket_hook.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import os
 
@@ -19,8 +19,8 @@ try:
     from flask_wtf import FlaskForm
 except ImportError:
     from flask_wtf import Form as FlaskForm
-from sqlalchemy.orm import relation
-from sqlalchemy.orm import backref
+
+from sqlalchemy.orm import backref, relation
 
 import pagure.lib.git
 import pagure.lib.tasks_services
@@ -31,7 +31,7 @@ from pagure.lib.model import BASE, Project
 
 
 class PagureTicketsTable(BASE):
-    """ Stores information about the pagure tickets hook deployed on a project.
+    """Stores information about the pagure tickets hook deployed on a project.
 
     Table -- hook_pagure_tickets
     """
@@ -62,11 +62,11 @@ class PagureTicketsTable(BASE):
 
 
 class PagureTicketRunner(BaseRunner):
-    """ Runner for the git hook updating the DB of tickets on push. """
+    """Runner for the git hook updating the DB of tickets on push."""
 
     @staticmethod
     def post_receive(session, username, project, repotype, repodir, changes):
-        """ Run the post-receive tasks of a hook.
+        """Run the post-receive tasks of a hook.
 
         For args, see BaseRunner.runhook.
         """
@@ -106,13 +106,13 @@ class PagureTicketRunner(BaseRunner):
 
 
 class PagureTicketsForm(FlaskForm):
-    """ Form to configure the pagure hook. """
+    """Form to configure the pagure hook."""
 
     active = wtforms.BooleanField("Active", [wtforms.validators.Optional()])
 
 
 class PagureTicketHook(BaseHook):
-    """ Pagure ticket hook. """
+    """Pagure ticket hook."""
 
     name = "Pagure tickets"
     description = (
@@ -128,7 +128,7 @@ class PagureTicketHook(BaseHook):
 
     @classmethod
     def set_up(cls, project):
-        """ Install the generic post-receive hook that allow us to call
+        """Install the generic post-receive hook that allow us to call
         multiple post-receive hooks as set per plugin.
         """
         repopath = os.path.join(pagure_config["TICKETS_FOLDER"], project.path)
@@ -152,7 +152,7 @@ class PagureTicketHook(BaseHook):
 
     @classmethod
     def install(cls, project, dbobj):
-        """ Method called to install the hook for a project.
+        """Method called to install the hook for a project.
 
         :arg project: a ``pagure.model.Project`` object to which the hook
             should be installed
@@ -168,7 +168,7 @@ class PagureTicketHook(BaseHook):
 
     @classmethod
     def remove(cls, project):
-        """ Method called to remove the hook of a project.
+        """Method called to remove the hook of a project.
 
         :arg project: a ``pagure.model.Project`` object to which the hook
             should be installed
diff --git a/pagure/hooks/pagure_unsigned_commits.py b/pagure/hooks/pagure_unsigned_commits.py
index bc3668f..09b89da 100644
--- a/pagure/hooks/pagure_unsigned_commits.py
+++ b/pagure/hooks/pagure_unsigned_commits.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import sqlalchemy as sa
 import wtforms
@@ -17,20 +17,19 @@ try:
     from flask_wtf import FlaskForm
 except ImportError:
     from flask_wtf import Form as FlaskForm
-from sqlalchemy.orm import relation
-from sqlalchemy.orm import backref
+
+from sqlalchemy.orm import backref, relation
 
 import pagure.config
 import pagure.lib.git
 from pagure.hooks import BaseHook, BaseRunner
 from pagure.lib.model import BASE, Project
 
-
 _config = pagure.config.reload_config()
 
 
 class PagureUnsignedCommitTable(BASE):
-    """ Stores information about the pagure hook deployed on a project.
+    """Stores information about the pagure hook deployed on a project.
 
     Table -- hook_pagure_unsigned_commit
     """
@@ -62,11 +61,11 @@ class PagureUnsignedCommitTable(BASE):
 
 
 class PagureUnsignerRunner(BaseRunner):
-    """ Runner for the hook blocking unsigned commits. """
+    """Runner for the hook blocking unsigned commits."""
 
     @staticmethod
     def pre_receive(session, username, project, repotype, repodir, changes):
-        """ Run the pre-receive tasks of a hook.
+        """Run the pre-receive tasks of a hook.
 
         For args, see BaseRunner.runhook.
         """
@@ -106,13 +105,13 @@ class PagureUnsignerRunner(BaseRunner):
 
 
 class PagureUnsignedCommitForm(FlaskForm):
-    """ Form to configure the pagure hook. """
+    """Form to configure the pagure hook."""
 
     active = wtforms.BooleanField("Active", [wtforms.validators.Optional()])
 
 
 class PagureUnsignedCommitHook(BaseHook):
-    """ PagurPagureUnsignedCommit hook. """
+    """PagurPagureUnsignedCommit hook."""
 
     name = "Block Un-Signed commits"
     description = (
diff --git a/pagure/hooks/rtd.py b/pagure/hooks/rtd.py
index 41b6d6f..c8430ce 100644
--- a/pagure/hooks/rtd.py
+++ b/pagure/hooks/rtd.py
@@ -9,18 +9,18 @@
 """
 
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
-import sqlalchemy as sa
 import requests
+import sqlalchemy as sa
 import wtforms
 
 try:
     from flask_wtf import FlaskForm
 except ImportError:
     from flask_wtf import Form as FlaskForm
-from sqlalchemy.orm import relation
-from sqlalchemy.orm import backref
+
+from sqlalchemy.orm import backref, relation
 
 import pagure
 from pagure.hooks import BaseHook, BaseRunner
@@ -30,7 +30,7 @@ _config = pagure.config.config
 
 
 class RtdTable(BASE):
-    """ Stores information about the pagure hook deployed on a project.
+    """Stores information about the pagure hook deployed on a project.
 
     Table -- hook_rtd
     """
@@ -65,7 +65,7 @@ class RtdTable(BASE):
 
 
 class RtdForm(FlaskForm):
-    """ Form to configure the pagure hook. """
+    """Form to configure the pagure hook."""
 
     api_url = wtforms.StringField(
         "URL endpoint used to trigger the builds",
@@ -102,7 +102,7 @@ will have to provide below.
 class RtdRunner(BaseRunner):
     @staticmethod
     def post_receive(session, username, project, repotype, repodir, changes):
-        """ Perform the RTD Post Receive hook.
+        """Perform the RTD Post Receive hook.
 
         For arguments, see BaseRunner.runhook.
         """
@@ -156,7 +156,7 @@ class RtdRunner(BaseRunner):
 
 
 class RtdHook(BaseHook):
-    """ Read The Doc hook. """
+    """Read The Doc hook."""
 
     name = "Read the Doc"
     description = DESCRIPTION
diff --git a/pagure/internal/__init__.py b/pagure/internal/__init__.py
index 25b6d87..9f00343 100644
--- a/pagure/internal/__init__.py
+++ b/pagure/internal/__init__.py
@@ -10,17 +10,16 @@ Internal endpoints.
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import collections
 import logging
 import os
+from functools import wraps
 
 import flask
 import pygit2
 import werkzeug.utils
-
-from functools import wraps
 from sqlalchemy.exc import SQLAlchemyError
 
 PV = flask.Blueprint("internal_ns", __name__, url_prefix="/pv")
@@ -31,11 +30,10 @@ import pagure.forms  # noqa: E402
 import pagure.lib.git  # noqa: E402
 import pagure.lib.query  # noqa: E402
 import pagure.lib.tasks  # noqa: E402
-import pagure.utils  # noqa: E402
 import pagure.ui.fork  # noqa: E402
+import pagure.utils  # noqa: E402
 from pagure.config import config as pagure_config  # noqa: E402
 
-
 _log = logging.getLogger(__name__)
 _auth_log = logging.getLogger("pagure_auth")
 
@@ -61,7 +59,7 @@ MERGE_OPTIONS = {
 
 
 def internal_access_only(function):
-    """ Decorator used to check if the request is iternal or not.
+    """Decorator used to check if the request is iternal or not.
 
     The request must either come from one of the addresses listed
     in IP_ALLOWED_INTERNAL or it must have the "Authentication"
@@ -71,8 +69,7 @@ def internal_access_only(function):
 
     @wraps(function)
     def decorated_function(*args, **kwargs):
-        """ Wrapped function actually checking if the request is local.
-        """
+        """Wrapped function actually checking if the request is local."""
         ip_allowed = pagure_config.get(
             "IP_ALLOWED_INTERNAL", ["127.0.0.1", "localhost", "::1"]
         )
@@ -95,7 +92,7 @@ def internal_access_only(function):
 @PV.route("/ssh/lookupkey/", methods=["POST"])
 @internal_access_only
 def lookup_ssh_key():
-    """ Looks up an SSH key by search_key for keyhelper.py """
+    """Looks up an SSH key by search_key for keyhelper.py"""
     search_key = flask.request.form["search_key"]
     username = flask.request.form.get("username")
     _auth_log.info(
@@ -126,7 +123,7 @@ def lookup_ssh_key():
 @PV.route("/ssh/checkaccess/", methods=["POST"])
 @internal_access_only
 def check_ssh_access():
-    """ Determines whether a user has read access to the requested repo. """
+    """Determines whether a user has read access to the requested repo."""
     gitdir = flask.request.form["gitdir"]
     remoteuser = flask.request.form["username"]
     _auth_log.info(
@@ -228,8 +225,7 @@ def check_ssh_access():
 @PV.route("/pull-request/comment/", methods=["PUT"])
 @internal_access_only
 def pull_request_add_comment():
-    """ Add a comment to a pull-request.
-    """
+    """Add a comment to a pull-request."""
     pform = pagure.forms.ProjectCommentForm(csrf_enabled=False)
     if not pform.validate_on_submit():
         flask.abort(400, description="Invalid request")
@@ -280,8 +276,7 @@ def pull_request_add_comment():
 @PV.route("/ticket/comment/", methods=["PUT"])
 @internal_access_only
 def ticket_add_comment():
-    """ Add a comment to an issue.
-    """
+    """Add a comment to an issue."""
     pform = pagure.forms.ProjectCommentForm(csrf_enabled=False)
     if not pform.validate_on_submit():
         flask.abort(400, description="Invalid request")
@@ -341,8 +336,7 @@ def ticket_add_comment():
 
 @PV.route("/pull-request/merge", methods=["POST"])
 def mergeable_request_pull():
-    """ Returns if the specified pull-request can be merged or not.
-    """
+    """Returns if the specified pull-request can be merged or not."""
     force = flask.request.form.get("force", False)
     if force is not False:
         force = True
@@ -413,7 +407,7 @@ def mergeable_request_pull():
 
 @PV.route("/pull-request/ready", methods=["POST"])
 def get_pull_request_ready_branch():
-    """ Return the list of branches that have commits not in the main
+    """Return the list of branches that have commits not in the main
     branch/repo (thus for which one could open a PR) and the number of
     commits that differ.
     """
@@ -480,8 +474,7 @@ def get_pull_request_ready_branch():
     "/fork/<username>/<namespace>/<repo>/issue/template", methods=["POST"]
 )
 def get_ticket_template(repo, namespace=None, username=None):
-    """ Return the template asked for the specified project
-    """
+    """Return the template asked for the specified project"""
 
     form = pagure.forms.ConfirmationForm()
     if not form.validate_on_submit():
@@ -542,8 +535,7 @@ def get_ticket_template(repo, namespace=None, username=None):
 
 @PV.route("/branches/commit/", methods=["POST"])
 def get_branches_of_commit():
-    """ Return the list of branches that have the specified commit in
-    """
+    """Return the list of branches that have the specified commit in"""
     form = pagure.forms.ConfirmationForm()
     if not form.validate_on_submit():
         response = flask.jsonify(
@@ -645,7 +637,7 @@ def get_branches_of_commit():
 
 @PV.route("/branches/heads/", methods=["POST"])
 def get_branches_head():
-    """ Return the heads of each branch in the repo, using the following
+    """Return the heads of each branch in the repo, using the following
     structure:
     {
         code: 'OK',
@@ -714,7 +706,7 @@ def get_branches_head():
 
 @PV.route("/task/<taskid>", methods=["GET"])
 def task_info(taskid):
-    """ Return the results of the specified task or a 418 if the task is
+    """Return the results of the specified task or a 418 if the task is
     still being processed.
     """
     task = pagure.lib.tasks.get_result(taskid)
@@ -730,9 +722,7 @@ def task_info(taskid):
 
 @PV.route("/stats/commits/authors", methods=["POST"])
 def get_stats_commits():
-    """ Return statistics about the commits made on the specified repo.
-
-    """
+    """Return statistics about the commits made on the specified repo."""
     form = pagure.forms.ConfirmationForm()
     if not form.validate_on_submit():
         response = flask.jsonify(
@@ -774,9 +764,7 @@ def get_stats_commits():
 
 @PV.route("/stats/commits/trend", methods=["POST"])
 def get_stats_commits_trend():
-    """ Return evolution of the commits made on the specified repo.
-
-    """
+    """Return evolution of the commits made on the specified repo."""
     form = pagure.forms.ConfirmationForm()
     if not form.validate_on_submit():
         response = flask.jsonify(
@@ -821,7 +809,7 @@ def get_stats_commits_trend():
 @PV.route("/fork/<username>/<repo>/family", methods=["POST"])
 @PV.route("/fork/<username>/<namespace>/<repo>/family", methods=["POST"])
 def get_project_family(repo, namespace=None, username=None):
-    """ Return the family of projects for the specified project
+    """Return the family of projects for the specified project
 
     {
         code: 'OK',
diff --git a/pagure/lib/encoding_utils.py b/pagure/lib/encoding_utils.py
index 66f7dce..95d84e0 100644
--- a/pagure/lib/encoding_utils.py
+++ b/pagure/lib/encoding_utils.py
@@ -11,15 +11,20 @@ the repetitive task of identifying the character encoding and decoding the
 content to unicode is implemented here.
 """
 
-from __future__ import unicode_literals, division, absolute_import
-from collections import namedtuple
+from __future__ import absolute_import, division, unicode_literals
+
 import logging
+from collections import namedtuple
 
-from chardet import universaldetector, __version__ as ch_version
+try:
+    import cchardet
+    from cchardet import __version__ as ch_version
+except ImportError:
+    cchardet = None
+    from chardet import universaldetector, __version__ as ch_version
 
 from pagure.exceptions import PagureEncodingException
 
-
 _log = logging.getLogger(__name__)
 
 Guess = namedtuple("Guess", ["encoding", "confidence"])
@@ -44,14 +49,26 @@ def detect_encodings(data):
 
     # We can't use ``chardet.detect`` because we want to dig in the internals
     # of the detector to bias the utf-8 result.
-    detector = universaldetector.UniversalDetector()
-    detector.reset()
-    detector.feed(data)
-    result = detector.close()
-    if not result:
+    if cchardet is not None:
+        detector = cchardet.UniversalDetector()
+        detector.reset()
+        detector.feed(data)
+        detector.close()
+        result = detector.result
+    else:
+        detector = universaldetector.UniversalDetector()
+        detector.reset()
+        detector.feed(data)
+        result = detector.close()
+
+    if not result or not result["encoding"]:
         return {"utf-8": 1.0}
     encodings = {result["encoding"]: result["confidence"]}
-    if ch_version[0] == "3":
+
+    if cchardet:
+        return encodings
+
+    if ch_version[0] in ("3", "4"):
         for prober in detector._charset_probers:
             if hasattr(prober, "probers"):
                 for prober in prober.probers:
diff --git a/pagure/lib/git.py b/pagure/lib/git.py
index 9a483a4..84bc914 100644
--- a/pagure/lib/git.py
+++ b/pagure/lib/git.py
@@ -7,13 +7,7 @@
    Pierre-Yves Chibon <pingou@pingoured.fr>
 
 """
-from __future__ import print_function, unicode_literals, absolute_import
-
-# pylint: disable=too-many-branches
-# pylint: disable=too-many-arguments
-# pylint: disable=too-many-locals
-# pylint: disable=too-many-statements
-# pylint: disable=too-many-lines
+from __future__ import absolute_import, print_function, unicode_literals
 
 import datetime
 import json
@@ -21,31 +15,34 @@ import logging
 import os
 import shutil
 import subprocess
-import requests
-import tempfile
 import tarfile
+import tempfile
 import zipfile
 
 import arrow
 import pygit2
+import requests
 import six
 
-from sqlalchemy.exc import SQLAlchemyError
-
 # from sqlalchemy.orm.session import Session
 from pygit2.remote import RemoteCollection
+from sqlalchemy.exc import SQLAlchemyError
 
-import pagure.utils
 import pagure.exceptions
-import pagure.lib.query
+import pagure.hooks
 import pagure.lib.notify
+import pagure.lib.query
 import pagure.lib.tasks
+import pagure.utils
 from pagure.config import config as pagure_config
 from pagure.lib import model
 from pagure.lib.repo import PagureRepo
-import pagure.hooks
 
-# from pagure.hooks import run_project_hooks
+# pylint: disable=too-many-branches
+# pylint: disable=too-many-arguments
+# pylint: disable=too-many-locals
+# pylint: disable=too-many-statements
+# pylint: disable=too-many-lines
 
 
 _log = logging.getLogger(__name__)
@@ -54,7 +51,7 @@ _log = logging.getLogger(__name__)
 def commit_to_patch(
     repo_obj, commits, diff_view=False, find_similar=False, separated=False
 ):
-    """ For a given commit (PyGit2 commit object) of a specified git repo,
+    """For a given commit (PyGit2 commit object) of a specified git repo,
     returns a string representation of the changes the commit did in a
     format that allows it to be used as patch.
 
@@ -145,7 +142,7 @@ Subject: {subject}
 
 
 def generate_gitolite_acls(project=None, group=None):
-    """ Generate the gitolite configuration file.
+    """Generate the gitolite configuration file.
 
     :arg project: the project of which to update the ACLs. This argument
             can take three values: ``-1``, ``None`` and a project.
@@ -175,7 +172,7 @@ def generate_gitolite_acls(project=None, group=None):
 
 
 def update_git(obj, repo):
-    """ Schedules an update_repo task after determining arguments. """
+    """Schedules an update_repo task after determining arguments."""
     ticketuid = None
     requestuid = None
     if obj.isa == "issue":
@@ -197,10 +194,10 @@ def update_git(obj, repo):
 
 
 def _maybe_wait(result):
-    """ Function to patch if one wants to wait for finish.
+    """Function to patch if one wants to wait for finish.
 
     This function should only ever be overridden by a few tests that depend
-    on counting and very precise timing. """
+    on counting and very precise timing."""
     pass
 
 
@@ -214,7 +211,7 @@ def _make_signature(name, email):
 
 
 def _update_git(obj, repo):
-    """ Update the given issue in its git.
+    """Update the given issue in its git.
 
     This method forks the provided repo, add/edit the issue whose file name
     is defined by the uid field of the issue and if there are additions/
@@ -313,9 +310,7 @@ def clean_git(repo, obj_repotype, obj_uid):
 
 
 def _clean_git(repo, obj_repotype, obj_uid):
-    """ Update the given issue remove it from its git.
-
-    """
+    """Update the given issue remove it from its git."""
     _log.info("Update the git repo: %s to remove: %s", repo.path, obj_uid)
 
     with TemporaryClone(repo, obj_repotype, "clean_git") as tempclone:
@@ -369,7 +364,7 @@ def _clean_git(repo, obj_repotype, obj_uid):
 
 
 def get_user_from_json(session, jsondata, key="user"):
-    """ From the given json blob, retrieve the user info and search for it
+    """From the given json blob, retrieve the user info and search for it
     in the db and create the user if it does not already exist.
     """
     user = None
@@ -412,7 +407,7 @@ def get_user_from_json(session, jsondata, key="user"):
 
 
 def get_project_from_json(session, jsondata):
-    """ From the given json blob, retrieve the project info and search for
+    """From the given json blob, retrieve the project info and search for
     it in the db and create the projec if it does not already exist.
     """
     project = None
@@ -468,7 +463,7 @@ def get_project_from_json(session, jsondata):
 
 
 def update_custom_field_from_json(session, repo, issue, json_data):
-    """ Update the custom fields according to the custom fields of
+    """Update the custom fields according to the custom fields of
     the issue. If the custom field is not present for the repo in
     it's settings, this will create them.
 
@@ -522,7 +517,7 @@ def update_custom_field_from_json(session, repo, issue, json_data):
 def update_ticket_from_git(
     session, reponame, namespace, username, issue_uid, json_data, agent
 ):
-    """ Update the specified issue (identified by its unique identifier)
+    """Update the specified issue (identified by its unique identifier)
     with the data present in the json blob provided.
 
     :arg session: the session to connect to the database with.
@@ -797,7 +792,7 @@ def update_ticket_from_git(
 def update_request_from_git(
     session, reponame, namespace, username, request_uid, json_data
 ):
-    """ Update the specified request (identified by its unique identifier)
+    """Update the specified request (identified by its unique identifier)
     with the data present in the json blob provided.
 
     :arg session: the session to connect to the database with.
@@ -897,7 +892,7 @@ def update_request_from_git(
 
 
 def _add_file_to_git(repo, issue, attachmentfolder, user, filename):
-    """ Add a given file to the specified ticket git repository.
+    """Add a given file to the specified ticket git repository.
 
     :arg repo: the Project object from the database
     :arg attachmentfolder: the folder on the filesystem where the attachments
@@ -982,7 +977,7 @@ class TemporaryClone(object):
     repo = None
 
     def __init__(self, project, repotype, action, path=None, parent=None):
-        """ Initializes a TempoaryClone instance.
+        """Initializes a TempoaryClone instance.
 
         Args:
             project (model.Project): A project instance
@@ -1007,7 +1002,7 @@ class TemporaryClone(object):
         self._parent = parent
 
     def __enter__(self):
-        """ Enter the context manager, creating the clone. """
+        """Enter the context manager, creating the clone."""
         self.repopath = tempfile.mkdtemp(prefix="pagure-%s-" % self._action)
         self._origrepopath = self.repopath
         if self._parent:
@@ -1091,11 +1086,11 @@ class TemporaryClone(object):
         return self
 
     def __exit__(self, exc_type, exc_value, traceback):
-        """ Exit the context manager, removing the temorary clone. """
+        """Exit the context manager, removing the temorary clone."""
         shutil.rmtree(self.repopath)
 
     def change_project_association(self, new_project):
-        """ Make this instance "belong" to another project.
+        """Make this instance "belong" to another project.
 
         This is useful when you want to create TemporaryClone of one project
         and then push some of its content into a different project just
@@ -1112,7 +1107,7 @@ class TemporaryClone(object):
             )
 
     def mirror(self, username, force=False, **extra):
-        """ Run ``git push --mirror`` of the repo to its origin.
+        """Run ``git push --mirror`` of the repo to its origin.
 
         Args:
             username (string): The user on who's account this push is
@@ -1125,7 +1120,7 @@ class TemporaryClone(object):
             self._push(username, "--mirror", force, **extra)
 
     def push(self, username, sbranch, tbranch=None, force=False, **extra):
-        """ Push the repo back to its origin.
+        """Push the repo back to its origin.
 
         Args:
             username (string): The user on who's account this push is
@@ -1139,7 +1134,7 @@ class TemporaryClone(object):
         self._push(username, pushref, force, **extra)
 
     def _push(self, username, pushref, force, **extra):
-        """ Push the repo back to its origin.
+        """Push the repo back to its origin.
 
         Args:
             username (string): The user on who's account this push is
@@ -1243,7 +1238,7 @@ class TemporaryClone(object):
 def _update_file_in_git(
     repo, branch, branchto, filename, content, message, user, email
 ):
-    """ Update a specific file in the specified repository with the content
+    """Update a specific file in the specified repository with the content
     given and commit the change under the user's name.
 
     :arg repo: the Project object from the database
@@ -1327,7 +1322,7 @@ def _update_file_in_git(
 
 
 def read_output(cmd, abspath, input=None, keepends=False, error=False, **kw):
-    """ Read the output from the given command to run.
+    """Read the output from the given command to run.
 
     cmd:
         The command to run, this is a list with each space separated into an
@@ -1411,7 +1406,7 @@ def read_git_lines(args, abspath, keepends=False, error=False, **kw):
 
 
 def get_revs_between(oldrev, newrev, abspath, refname, forced=False):
-    """ Yield revisions between HEAD and BASE. """
+    """Yield revisions between HEAD and BASE."""
 
     cmd = ["rev-list", "%s...%s" % (oldrev, newrev)]
     if forced:
@@ -1428,7 +1423,7 @@ def get_revs_between(oldrev, newrev, abspath, refname, forced=False):
 
 
 def is_forced_push(oldrev, newrev, abspath):
-    """ Returns whether there was a force push between HEAD and BASE.
+    """Returns whether there was a force push between HEAD and BASE.
     Doc: http://stackoverflow.com/a/12258773
     """
 
@@ -1442,7 +1437,7 @@ def is_forced_push(oldrev, newrev, abspath):
 
 
 def get_base_revision(torev, fromrev, abspath):
-    """ Return the base revision between HEAD and BASE.
+    """Return the base revision between HEAD and BASE.
     This is useful in case of force-push.
     """
     cmd = ["merge-base", fromrev, torev]
@@ -1450,7 +1445,7 @@ def get_base_revision(torev, fromrev, abspath):
 
 
 def get_default_branch(abspath):
-    """ Return the default branch of a repo. """
+    """Return the default branch of a repo."""
     cmd = ["rev-parse", "--abbrev-ref", "HEAD"]
     out = pagure.lib.git.read_git_lines(cmd, abspath)
     if out:
@@ -1460,7 +1455,7 @@ def get_default_branch(abspath):
 
 
 def get_author(commit, abspath):
-    """ Return the name of the person that authored the commit. """
+    """Return the name of the person that authored the commit."""
     user = pagure.lib.git.read_git_lines(
         ["log", "-1", '--pretty=format:"%an"', commit], abspath
     )[0].replace('"', "")
@@ -1468,7 +1463,7 @@ def get_author(commit, abspath):
 
 
 def get_author_email(commit, abspath):
-    """ Return the email of the person that authored the commit. """
+    """Return the email of the person that authored the commit."""
     user = pagure.lib.git.read_git_lines(
         ["log", "-1", '--pretty=format:"%ae"', commit], abspath
     )[0].replace('"', "")
@@ -1476,7 +1471,7 @@ def get_author_email(commit, abspath):
 
 
 def get_commit_subject(commit, abspath):
-    """ Return the subject of the commit. """
+    """Return the subject of the commit."""
     subject = pagure.lib.git.read_git_lines(
         ["log", "-1", '--pretty=format:"%s"', commit], abspath
     )[0].replace('"', "")
@@ -1484,7 +1479,7 @@ def get_commit_subject(commit, abspath):
 
 
 def get_repo_info_from_path(gitdir, hide_notfound=False):
-    """ Returns the name, username, namespace and type of a git directory
+    """Returns the name, username, namespace and type of a git directory
 
     This gets computed based on the *_FOLDER's in the config file,
     and as such only works for the central file-based repositories.
@@ -1591,29 +1586,25 @@ def get_repo_info_from_path(gitdir, hide_notfound=False):
 
 
 def get_repo_name(abspath):
-    """ Return the name of the git repo based on its path.
-    """
+    """Return the name of the git repo based on its path."""
     _, _, _, name = get_repo_info_from_path(abspath)
     return name
 
 
 def get_repo_namespace(abspath, gitfolder=None):
-    """ Return the name of the git repo based on its path.
-    """
+    """Return the name of the git repo based on its path."""
     _, _, namespace, _ = get_repo_info_from_path(abspath)
     return namespace
 
 
 def get_username(abspath):
-    """ Return the username of the git repo based on its path.
-    """
+    """Return the username of the git repo based on its path."""
     _, username, _, _ = get_repo_info_from_path(abspath)
     return username
 
 
 def get_branch_ref(repo, branchname):
-    """ Return the reference to the specified branch or raises an exception.
-    """
+    """Return the reference to the specified branch or raises an exception."""
     location = pygit2.GIT_BRANCH_LOCAL
     if branchname not in repo.listall_branches():
         branchname = "origin/%s" % branchname
@@ -1628,8 +1619,7 @@ def get_branch_ref(repo, branchname):
 
 
 def merge_pull_request(session, request, username, domerge=True):
-    """ Merge the specified pull-request.
-    """
+    """Merge the specified pull-request."""
     if domerge:
         _log.info("%s asked to merge the pull-request: %s", username, request)
     else:
@@ -1955,7 +1945,7 @@ def merge_pull_request(session, request, username, domerge=True):
 
 
 def rebase_pull_request(session, request, username):
-    """ Rebase the specified pull-request.
+    """Rebase the specified pull-request.
 
     Args:
         session (sqlalchemy): the session to connect to the database with
@@ -2094,7 +2084,7 @@ def rebase_pull_request(session, request, username):
 
 
 def get_diff_info(repo_obj, orig_repo, branch_from, branch_to, prid=None):
-    """ Return the info needed to see a diff or make a Pull-Request between
+    """Return the info needed to see a diff or make a Pull-Request between
     the two specified repo.
 
     :arg repo_obj: The pygit2.Repository object of the first git repo
@@ -2261,7 +2251,7 @@ def diff_pull_request(
     notify=True,
     username=None,
 ):
-    """ Returns the diff and the list of commits between the two git repos
+    """Returns the diff and the list of commits between the two git repos
     mentionned in the given pull-request.
 
     :arg session: The sqlalchemy session to connect to the database
@@ -2278,7 +2268,7 @@ def diff_pull_request(
     _log.debug("pagure.lib.git.diff_pull_request, started")
     diff = None
     diff_commits = []
-    diff, diff_commits, _ = get_diff_info(
+    diff, diff_commits, orig_commit = get_diff_info(
         repo_obj,
         orig_repo,
         request.branch_from,
@@ -2324,7 +2314,10 @@ def diff_pull_request(
                 and request.commit_start != first_commit.oid.hex
             ):
                 pr_action = "rebased"
-                commenttext = "rebased onto %s" % first_commit.oid.hex
+                if orig_commit:
+                    commenttext = "rebased onto %s" % orig_commit.oid.hex
+                else:
+                    commenttext = "rebased onto unknown target"
         request.commit_start = first_commit.oid.hex
         request.commit_stop = diff_commits[0].oid.hex
         session.add(request)
@@ -2383,8 +2376,7 @@ def diff_pull_request(
 
 
 def update_pull_ref(request, repo):
-    """ Create or update the refs/pull/ reference in the git repo.
-    """
+    """Create or update the refs/pull/ reference in the git repo."""
 
     repopath = pagure.utils.get_repo_path(request.project)
     reponame = "%s_%s" % (request.user.user, request.uid)
@@ -2422,7 +2414,7 @@ def update_pull_ref(request, repo):
 
 
 def get_git_tags(project, with_commits=False):
-    """ Returns the list of tags created in the git repositorie of the
+    """Returns the list of tags created in the git repositorie of the
     specified project.
     """
     repopath = pagure.utils.get_repo_path(project)
@@ -2448,7 +2440,7 @@ def get_git_tags(project, with_commits=False):
 
 
 def new_git_tag(project, tagname, target, user, message=None, force=False):
-    """ Create a new git tag in the git repositorie of the specified project.
+    """Create a new git tag in the git repositorie of the specified project.
 
     :arg project: the project in which we want to create a git tag
     :type project: pagure.lib.model.Project
@@ -2486,9 +2478,9 @@ def new_git_tag(project, tagname, target, user, message=None, force=False):
 
 
 def get_git_tags_objects(project):
-    """ Returns the list of references of the tags created in the git
+    """Returns the list of references of the tags created in the git
     repositorie the specified project.
-    The list is sorted using the time of the commit associated to the tag """
+    The list is sorted using the time of the commit associated to the tag"""
     repopath = pagure.utils.get_repo_path(project)
     repo_obj = PagureRepo(repopath)
     tags = {}
@@ -2535,7 +2527,7 @@ def get_git_tags_objects(project):
 
 
 def log_commits_to_db(session, project, commits, gitdir):
-    """ Log the given commits to the DB. """
+    """Log the given commits to the DB."""
     repo_obj = PagureRepo(gitdir)
 
     for commitid in commits:
@@ -2566,7 +2558,7 @@ def log_commits_to_db(session, project, commits, gitdir):
 
 
 def reinit_git(project, repofolder):
-    """ Delete and recreate a git folder
+    """Delete and recreate a git folder
     :args project: SQLAlchemy object of the project
     :args folder: The folder which contains the git repos
     like TICKETS_FOLDER for tickets and REQUESTS_FOLDER for
@@ -2587,7 +2579,7 @@ def reinit_git(project, repofolder):
 
 
 def get_git_branches(project, with_commits=False):
-    """ Return a list of branches for the project
+    """Return a list of branches for the project
     :arg project: The Project instance to get the branches for
     :arg with_commits: Whether we should return branch head commits or not
     """
@@ -2609,7 +2601,7 @@ def get_git_branches(project, with_commits=False):
 
 
 def get_default_git_branches(project):
-    """ Return a tuple of the default branchname and its head commit hash
+    """Return a tuple of the default branchname and its head commit hash
     :arg project: The Project instance to get the branches for
     """
     repo_path = pagure.utils.get_repo_path(project)
@@ -2624,7 +2616,7 @@ def get_default_git_branches(project):
 def new_git_branch(
     username, project, branch, from_branch=None, from_commit=None
 ):
-    """ Create a new git branch on the project
+    """Create a new git branch on the project
     :arg project: The Project instance to get the branches for
     :arg from_branch: The branch to branch off of
     """
@@ -2659,7 +2651,7 @@ def new_git_branch(
 
 
 def git_set_ref_head(project, branch):
-    """ Set the HEAD reference of the project
+    """Set the HEAD reference of the project
     :arg project: The project instance to set the HEAD reference
     :arg branch: The branch to be set as HEAD reference
     """
@@ -2670,8 +2662,64 @@ def git_set_ref_head(project, branch):
     repo_obj.set_head(reference.name)
 
 
+def get_branch_aliases(project):
+    """Iterates through the references of the provided git repo to extract all
+    of its aliases.
+    """
+    repo_path = pagure.utils.get_repo_path(project)
+    repo_obj = PagureRepo(repo_path)
+
+    output = {}
+    for ref in repo_obj.listall_reference_objects():
+        if "refs/heads/" in str(ref.target):
+            output[ref.name] = ref.target
+    return output
+
+
+def set_branch_alias(project, source, dest):
+    """Create a reference in the provided git repo from the source reference
+    to the dest one.
+    """
+    repo_path = pagure.utils.get_repo_path(project)
+    repo_obj = PagureRepo(repo_path)
+
+    # Check that the source reference exists
+    repo_obj.lookup_reference("refs/heads/{}".format(dest))
+
+    try:
+        repo_obj.create_reference(
+            "refs/heads/{}".format(source),
+            "refs/heads/{}".format(dest),
+        )
+    except ValueError as err:
+        _log.debug(
+            "Failed to create alias from %s to %s -- %s", source, dest, err
+        )
+        raise pagure.exceptions.PagureException(
+            "Could not create alias from {0} to {1}. "
+            "Reference already existing?".format(source, dest)
+        )
+
+
+def drop_branch_aliases(project, source, dest):
+    """Delete a reference in the provided git repo from the source reference
+    to the dest one.
+    """
+    repo_path = pagure.utils.get_repo_path(project)
+    repo_obj = PagureRepo(repo_path)
+
+    ref = repo_obj.lookup_reference("refs/heads/{}".format(dest))
+    output = False
+    if ref.target == "refs/heads/{}".format(source):
+        ref_file = os.path.join(repo_obj.path, ref.name)
+        if os.path.exists(ref_file):
+            os.unlink(ref_file)
+            output = True
+    return output
+
+
 def delete_project_repos(project):
-    """ Deletes the actual git repositories on disk or repoSpanner
+    """Deletes the actual git repositories on disk or repoSpanner
 
     Args:
         project (Project): Project to delete repos for
@@ -2719,7 +2767,7 @@ def delete_project_repos(project):
 
 
 def set_up_project_hooks(project, region, hook=None):
-    """ Makes sure the git repositories for a project have their hooks setup.
+    """Makes sure the git repositories for a project have their hooks setup.
 
     Args:
         project (model.Project): Project to set up hooks for
@@ -2769,7 +2817,7 @@ def set_up_project_hooks(project, region, hook=None):
 
 
 def _create_project_repo(project, region, templ, ignore_existing, repotype):
-    """ Creates a single specific git repository on disk or repoSpanner
+    """Creates a single specific git repository on disk or repoSpanner
 
     Args:
         project (Project): Project to create repos for
@@ -2846,7 +2894,7 @@ def _create_project_repo(project, region, templ, ignore_existing, repotype):
 
 
 def create_project_repos(project, region, templ, ignore_existing):
-    """ Creates the actual git repositories on disk or repoSpanner
+    """Creates the actual git repositories on disk or repoSpanner
 
     Args:
         project (Project): Project to create repos for
@@ -2875,7 +2923,7 @@ def create_project_repos(project, region, templ, ignore_existing):
 
 
 def get_stats_patch(patch):
-    """ Returns some statistics about a given patch.
+    """Returns some statistics about a given patch.
 
     These stats include:
         status: if the file was added (A), deleted (D), modified (M) or
@@ -2953,7 +3001,7 @@ def get_stats_patch(patch):
 
 
 def generate_archive(project, commit, tag, name, archive_fmt):
-    """ Generate the desired archive of the specified project for the
+    """Generate the desired archive of the specified project for the
     specified commit with the given name and archive format.
 
     Args:
@@ -3028,7 +3076,7 @@ def generate_archive(project, commit, tag, name, archive_fmt):
 
 
 def mirror_pull_project(session, project, debug=False):
-    """ Mirror locally a project from a remote URL. """
+    """Mirror locally a project from a remote URL."""
     remote = project.mirrored_from
     if not remote:
         _log.info("No remote found, ignoring")
@@ -3056,7 +3104,7 @@ def mirror_pull_project(session, project, debug=False):
 
     try:
         # Pull
-        logs = []
+        logs = ["Run from: %s" % datetime.datetime.utcnow().isoformat()]
         logs = _run_command(["clone", "--mirror", remote, "."], logs)
         logs = _run_command(["remote", "add", "local", lclrepopath], logs)
 
diff --git a/pagure/lib/git_auth.py b/pagure/lib/git_auth.py
index b354a51..c4c0892 100644
--- a/pagure/lib/git_auth.py
+++ b/pagure/lib/git_auth.py
@@ -7,17 +7,17 @@
    Pierre-Yves Chibon <pingou@pingoured.fr>
 
 """
-from __future__ import print_function, unicode_literals, absolute_import
+from __future__ import absolute_import, print_function, unicode_literals
 
 import abc
 import json
 import logging
 import os
-import pkg_resources
 import subprocess
 import tempfile
 from io import open
 
+import pkg_resources
 import werkzeug.utils
 from six import with_metaclass
 from six.moves import dbm_gnu
@@ -29,7 +29,6 @@ from pagure.config import config as pagure_config
 from pagure.lib import model
 from pagure.utils import is_repo_collaborator, lookup_deploykey
 
-
 # logging.config.dictConfig(pagure_config.get('LOGGING') or {'version': 1})
 _log = logging.getLogger(__name__)
 
@@ -38,7 +37,7 @@ GIT_AUTH_BACKEND_INSTANCE = None
 
 
 def get_git_auth_helper(backend=None):
-    """ Instantiate and return the appropriate git auth helper backend.
+    """Instantiate and return the appropriate git auth helper backend.
 
     :arg backend: The name of the backend to find on the system (declared via
         the entry_points in setup.py).
@@ -86,7 +85,7 @@ def get_git_auth_helper(backend=None):
 
 
 class GitAuthHelper(with_metaclass(abc.ABCMeta, object)):
-    """ The class to inherit from when creating your own git authentication
+    """The class to inherit from when creating your own git authentication
     helper.
     """
 
@@ -95,7 +94,7 @@ class GitAuthHelper(with_metaclass(abc.ABCMeta, object)):
     @classmethod
     @abc.abstractmethod
     def generate_acls(self, project, group=None):
-        """ This is the method that is called by pagure to generate the
+        """This is the method that is called by pagure to generate the
         configuration file.
 
         :arg project: the project of which to update the ACLs. This argument
@@ -121,7 +120,7 @@ class GitAuthHelper(with_metaclass(abc.ABCMeta, object)):
     @classmethod
     @abc.abstractmethod
     def remove_acls(self, session, project):
-        """ This is the method that is called by pagure to remove a project
+        """This is the method that is called by pagure to remove a project
         from the configuration file.
 
         :arg cls: the current class
@@ -138,7 +137,7 @@ class GitAuthHelper(with_metaclass(abc.ABCMeta, object)):
     # This method can't be marked as abstract, since it's new and that would
     # break backwards compatibility
     def check_acl(cls, session, project, username, refname, **info):
-        """ This method is used in Dynamic Git Auth helpers to check acls.
+        """This method is used in Dynamic Git Auth helpers to check acls.
 
         It is acceptable for implementations to print things, which will be
         returned to the user.
@@ -182,7 +181,7 @@ class GitAuthHelper(with_metaclass(abc.ABCMeta, object)):
 
 
 def _read_file(filename):
-    """ Reads the specified file and return its content.
+    """Reads the specified file and return its content.
     Returns None if it could not read the file for any reason.
     """
     if not os.path.exists(filename):
@@ -193,11 +192,11 @@ def _read_file(filename):
 
 
 class Gitolite2Auth(GitAuthHelper):
-    """ A gitolite 2 authentication module. """
+    """A gitolite 2 authentication module."""
 
     @classmethod
     def _process_project(cls, project, config, global_pr_only):
-        """ Generate the gitolite configuration for the specified project.
+        """Generate the gitolite configuration for the specified project.
 
         :arg project: the project to generate the configuration for
         :type project: pagure.lib.model.Project
@@ -281,7 +280,7 @@ class Gitolite2Auth(GitAuthHelper):
 
     @classmethod
     def _clean_current_config(cls, current_config, project):
-        """ Remove the specified project from the current configuration file
+        """Remove the specified project from the current configuration file
 
         :arg current_config: the content of the current/actual gitolite
             configuration file read from the disk
@@ -314,7 +313,7 @@ class Gitolite2Auth(GitAuthHelper):
 
     @classmethod
     def _clean_groups(cls, config, group=None):
-        """ Removes the groups in the given configuration file.
+        """Removes the groups in the given configuration file.
 
         :arg config: the current configuration
         :type config: list
@@ -361,7 +360,7 @@ class Gitolite2Auth(GitAuthHelper):
 
     @classmethod
     def _generate_groups_config(cls, session):
-        """ Generate the gitolite configuration for all of the groups.
+        """Generate the gitolite configuration for all of the groups.
 
         :arg session: the session with which to connect to the database
         :return: the gitolite configuration for the groups
@@ -380,7 +379,7 @@ class Gitolite2Auth(GitAuthHelper):
 
     @classmethod
     def _get_current_config(cls, configfile, preconfig=None, postconfig=None):
-        """ Load the current gitolite configuration file from the disk.
+        """Load the current gitolite configuration file from the disk.
 
         :arg configfile: the name of the configuration file to load
         :type configfile: str
@@ -435,7 +434,7 @@ class Gitolite2Auth(GitAuthHelper):
         postconf=None,
         group=None,
     ):
-        """ Generate the configuration file for gitolite for all projects
+        """Generate the configuration file for gitolite for all projects
         on the forge.
 
         :arg cls: the current class
@@ -585,7 +584,7 @@ class Gitolite2Auth(GitAuthHelper):
 
     @classmethod
     def remove_acls(cls, session, project):
-        """ Remove a project from the configuration file for gitolite.
+        """Remove a project from the configuration file for gitolite.
 
         :arg cls: the current class
         :type: Gitolite2Auth
@@ -685,7 +684,7 @@ class Gitolite2Auth(GitAuthHelper):
 
     @staticmethod
     def _get_gitolite_command():
-        """ Return the gitolite command to run based on the info in the
+        """Return the gitolite command to run based on the info in the
         configuration file.
         """
         _log.info("Compiling the gitolite configuration")
@@ -700,7 +699,7 @@ class Gitolite2Auth(GitAuthHelper):
 
     @classmethod
     def _repos_from_lines(cls, lines):
-        """ Return list of strings representing complete repo entries from list
+        """Return list of strings representing complete repo entries from list
         of lines as returned by _process_project.
         """
         repos = []
@@ -715,7 +714,7 @@ class Gitolite2Auth(GitAuthHelper):
 
     @classmethod
     def _run_gitolite_cmd(cls, cmd):
-        """ Run gitolite command as subprocess, raise PagureException
+        """Run gitolite command as subprocess, raise PagureException
         if it fails.
         """
         if cmd:
@@ -738,7 +737,7 @@ class Gitolite2Auth(GitAuthHelper):
 
     @classmethod
     def generate_acls(cls, project, group=None):
-        """ Generate the gitolite configuration file for all repos
+        """Generate the gitolite configuration file for all repos
 
         :arg project: the project to update in the gitolite configuration
             file. It can be of three types/values.
@@ -805,7 +804,7 @@ class Gitolite2Auth(GitAuthHelper):
 
 
 class Gitolite3Auth(Gitolite2Auth):
-    """ A gitolite 3 authentication module. """
+    """A gitolite 3 authentication module."""
 
     @staticmethod
     def _individual_repos_command(config_file):
@@ -824,7 +823,7 @@ class Gitolite3Auth(Gitolite2Auth):
 
     @staticmethod
     def _get_gitolite_command():
-        """ Return the gitolite command to run based on the info in the
+        """Return the gitolite command to run based on the info in the
         configuration file.
         """
         _log.info("Compiling the gitolite configuration")
@@ -839,7 +838,7 @@ class Gitolite3Auth(Gitolite2Auth):
 
     @classmethod
     def post_compile_only(cls):
-        """ This method runs `gitolite trigger POST_COMPILE` without touching
+        """This method runs `gitolite trigger POST_COMPILE` without touching
         any other gitolite configuration. Most importantly, this will process
         SSH keys used by gitolite.
         """
@@ -852,24 +851,24 @@ class Gitolite3Auth(Gitolite2Auth):
 
 
 class PagureGitAuth(GitAuthHelper):
-    """ Standard Pagure git auth implementation. """
+    """Standard Pagure git auth implementation."""
 
     is_dynamic = True
 
     @classmethod
     def generate_acls(self, project, group=None):
-        """ This function is required but not used. """
+        """This function is required but not used."""
         pass
 
     @classmethod
     def remove_acls(self, session, project):
-        """ This function is required but not used. """
+        """This function is required but not used."""
         pass
 
     def info(self, msg):
-        """ Function that prints info about decisions to clients.
+        """Function that prints info about decisions to clients.
 
-        This is a function to make it possible to override for test suite. """
+        This is a function to make it possible to override for test suite."""
         print(msg)
 
     def check_acl(
@@ -914,13 +913,13 @@ class PagureGitAuth(GitAuthHelper):
 
 
 class GitAuthTestHelper(GitAuthHelper):
-    """ Simple test auth module to check the auth customization system. """
+    """Simple test auth module to check the auth customization system."""
 
     is_dynamic = True
 
     @classmethod
     def generate_acls(cls, project, group=None):
-        """ Print a statement when called, useful for debugging, only.
+        """Print a statement when called, useful for debugging, only.
 
         :arg project: this variable is just printed out but not used
             in any real place.
@@ -938,7 +937,7 @@ class GitAuthTestHelper(GitAuthHelper):
 
     @classmethod
     def remove_acls(cls, session, project):
-        """ Print a statement about which a project would be removed from
+        """Print a statement about which a project would be removed from
         the configuration file for gitolite.
 
         :arg cls: the current class
diff --git a/pagure/lib/lib_ci.py b/pagure/lib/lib_ci.py
index 058b468..fac71d6 100644
--- a/pagure/lib/lib_ci.py
+++ b/pagure/lib/lib_ci.py
@@ -10,14 +10,14 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 # pylint: disable=too-many-locals
 import logging
 import time
+
 import pagure.exceptions
 import pagure.lib.query
-
 from pagure.config import config as pagure_config
 
 _log = logging.getLogger(__name__)
@@ -31,7 +31,7 @@ BUILD_STATS = {
 
 
 def process_jenkins_build(session, project, build_id, iteration=0):
-    """  Gets the build info from jenkins and flags that particular
+    """Gets the build info from jenkins and flags that particular
     pull-request.
     """
     import jenkins
@@ -145,7 +145,7 @@ def trigger_jenkins_build(
     ci_username=None,
     ci_password=None,
 ):
-    """ Trigger a build on a jenkins instance."""
+    """Trigger a build on a jenkins instance."""
     try:
         import jenkins
     except ImportError:
diff --git a/pagure/lib/link.py b/pagure/lib/link.py
index a2ecec2..7c778ca 100644
--- a/pagure/lib/link.py
+++ b/pagure/lib/link.py
@@ -10,15 +10,15 @@
 
 # pylint: disable=too-many-arguments
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import re
-import pagure.lib.query
+
 import pagure.exceptions
+import pagure.lib.query
 import pagure.utils
 from pagure.config import config as pagure_config
 
-
 FIXES = [
     re.compile(r"(?:.*\s+)?{0}?[sd]?:?\s*?#(\d+)".format(kw), re.I)
     for kw in ["fix", "fixe", "merge", "close", "resolve"]
@@ -59,7 +59,7 @@ def get_relation(
     reftype="relates",
     include_prs=False,
 ):
-    """ For a given text, searches using regex if the text contains
+    """For a given text, searches using regex if the text contains
     reference to another issue in this project or another one.
 
     Returns the list of issues referenced (possibly empty).
diff --git a/pagure/lib/login.py b/pagure/lib/login.py
index e34363c..3a0760a 100644
--- a/pagure/lib/login.py
+++ b/pagure/lib/login.py
@@ -9,7 +9,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 try:
     # Provided in Python 3.6+
@@ -21,18 +21,19 @@ except ImportError:
     random = random.SystemRandom()
     random_choice = random.choice
 
-import string
 import hashlib
+import string
+
 import bcrypt
 import six
+from cryptography.hazmat.primitives import constant_time
 
 import pagure.config
 from pagure.lib import model
-from cryptography.hazmat.primitives import constant_time
 
 
 def id_generator(size=15, chars=string.ascii_uppercase + string.digits):
-    """ Generates a random identifier for the given size and using the
+    """Generates a random identifier for the given size and using the
     specified characters.
     If no size is specified, it uses 15 as default.
     If no characters are specified, it uses ascii char upper case and
@@ -45,7 +46,7 @@ def id_generator(size=15, chars=string.ascii_uppercase + string.digits):
 
 
 def get_session_by_visitkey(session, sessionid):
-    """ Return a specified VisitUser via its session identifier (visit_key).
+    """Return a specified VisitUser via its session identifier (visit_key).
 
     :arg session: the session with which to connect to the database.
 
@@ -58,7 +59,7 @@ def get_session_by_visitkey(session, sessionid):
 
 
 def generate_hashed_value(password):
-    """ Generate hash value for password.
+    """Generate hash value for password.
 
     :arg password: password for which the hash has to be generated.
     :type password: str (Python 3) or unicode (Python 2)
@@ -74,7 +75,7 @@ def generate_hashed_value(password):
 
 
 def check_password(entered_password, user_password, seed=None):
-    """ Version checking and returning the password
+    """Version checking and returning the password
 
     :arg entered_password: password entered by the user.
     :type entered_password: str (Python 3) or unicode (Python 2)
@@ -116,7 +117,7 @@ def check_password(entered_password, user_password, seed=None):
 
 
 def check_username_and_password(session, username, password):
-    """ Check if the provided username and password match what is in the
+    """Check if the provided username and password match what is in the
     database and raise an pagure.exceptions.PagureException if that is
     not the case.
     """
diff --git a/pagure/lib/mimetype.py b/pagure/lib/mimetype.py
index 44187c6..b8ec46f 100644
--- a/pagure/lib/mimetype.py
+++ b/pagure/lib/mimetype.py
@@ -1,15 +1,15 @@
 # -*- coding: utf-8 -*-
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import logging
 import mimetypes
+
 import kitchen.text.converters as ktc
 import six
 
 import pagure.lib.encoding_utils
 
-
 _log = logging.getLogger(__name__)
 
 
diff --git a/pagure/lib/model.py b/pagure/lib/model.py
index 325a545..252d17c 100644
--- a/pagure/lib/model.py
+++ b/pagure/lib/model.py
@@ -8,29 +8,30 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
-import arrow
-import datetime
 import collections
-import logging
+import datetime
 import json
+import logging
 import operator
-import re
-import pygit2
 import os
+import re
 from operator import attrgetter
 
+import arrow
+import pygit2
 import six
 import sqlalchemy as sa
-
 from sqlalchemy import create_engine
 from sqlalchemy.exc import SQLAlchemyError
-from sqlalchemy.orm import backref
-from sqlalchemy.orm import sessionmaker
-from sqlalchemy.orm import scoped_session
-from sqlalchemy.orm import relation
-from sqlalchemy.orm import validates
+from sqlalchemy.orm import (
+    backref,
+    relation,
+    scoped_session,
+    sessionmaker,
+    validates,
+)
 
 import pagure.exceptions
 from pagure.config import config as pagure_config
@@ -38,7 +39,6 @@ from pagure.lib.model_base import BASE
 from pagure.lib.plugins import get_plugin_tables
 from pagure.utils import is_true
 
-
 _log = logging.getLogger(__name__)
 
 # hit w/ all the id field we use
@@ -49,7 +49,7 @@ _log = logging.getLogger(__name__)
 
 
 def create_tables(db_url, alembic_ini=None, acls=None, debug=False):
-    """ Create the tables in the database using the information from the
+    """Create the tables in the database using the information from the
     url obtained.
 
     :arg db_url, URL used to connect to the database. The URL contains
@@ -75,7 +75,7 @@ def create_tables(db_url, alembic_ini=None, acls=None, debug=False):
         # Ignore the warning about con_record
         # pylint: disable=unused-argument
         def _fk_pragma_on_connect(dbapi_con, _):  # pragma: no cover
-            """ Tries to enforce referential constraints on sqlite. """
+            """Tries to enforce referential constraints on sqlite."""
             dbapi_con.execute("pragma foreign_keys=ON")
 
         sa.event.listen(engine, "connect", _fk_pragma_on_connect)
@@ -86,8 +86,8 @@ def create_tables(db_url, alembic_ini=None, acls=None, debug=False):
 
         # Ignore the warning missing alembic
         # pylint: disable=import-error
-        from alembic.config import Config
         from alembic import command
+        from alembic.config import Config
 
         alembic_cfg = Config(alembic_ini)
         command.stamp(alembic_cfg, "head")
@@ -100,8 +100,7 @@ def create_tables(db_url, alembic_ini=None, acls=None, debug=False):
 
 
 def create_default_status(session, acls=None):
-    """ Insert the defaults status in the status tables.
-    """
+    """Insert the defaults status in the status tables."""
 
     statuses = ["Open", "Closed"]
     for status in statuses:
@@ -157,7 +156,7 @@ def arrow_ts(value):
 
 
 class AccessLevels(BASE):
-    """ Different access levels a user/group can have for a project """
+    """Different access levels a user/group can have for a project"""
 
     __tablename__ = "access_levels"
 
@@ -165,7 +164,7 @@ class AccessLevels(BASE):
 
 
 class StatusIssue(BASE):
-    """ Stores the status a ticket can have.
+    """Stores the status a ticket can have.
 
     Table -- status_issue
     """
@@ -177,7 +176,7 @@ class StatusIssue(BASE):
 
 
 class StatusPullRequest(BASE):
-    """ Stores the status a pull-request can have.
+    """Stores the status a pull-request can have.
 
     Table -- status_issue
     """
@@ -189,7 +188,7 @@ class StatusPullRequest(BASE):
 
 
 class User(BASE):
-    """ Stores information about users.
+    """Stores information about users.
 
     Table -- users
     """
@@ -227,19 +226,18 @@ class User(BASE):
 
     @property
     def username(self):
-        """ Return the username. """
+        """Return the username."""
         return self.user
 
     @property
     def url_path(self):
-        """ Return the path at which this user can be accessed in the web UI.
-        """
+        """Return the path at which this user can be accessed in the web UI."""
 
         return "user/%s" % (self.user)
 
     @property
     def html_title(self):
-        """ Return the ``fullname (username)`` or simply ``username`` to be
+        """Return the ``fullname (username)`` or simply ``username`` to be
         used in the html templates.
         """
         if self.fullname:
@@ -249,12 +247,12 @@ class User(BASE):
 
     @property
     def groups(self):
-        """ Return the list of Group.group_name in which the user is. """
+        """Return the list of Group.group_name in which the user is."""
         return [group.group_name for group in self.group_objs]
 
     @property
     def settings(self):
-        """ Return the dict stored as string in the database as an actual
+        """Return the dict stored as string in the database as an actual
         dict object.
         """
         default = {"cc_me_to_my_actions": False}
@@ -273,20 +271,27 @@ class User(BASE):
 
     @settings.setter
     def settings(self, settings):
-        """ Ensures the settings are properly saved. """
+        """Ensures the settings are properly saved."""
         self._settings = json.dumps(settings)
 
+    @property
+    def full_url(self):
+        """Returns the default status of the board."""
+        base_url = pagure_config["APP_URL"].rstrip("/")
+        return "/".join([base_url, "user", self.user])
+
     def __repr__(self):
-        """ Return a string representation of this object. """
+        """Return a string representation of this object."""
 
         return "User: %s - name %s" % (self.id, self.user)
 
     def to_json(self, public=False):
-        """ Return a representation of the User in a dictionary. """
+        """Return a representation of the User in a dictionary."""
         output = {
             "name": self.user,
             "fullname": self.fullname,
             "url_path": self.url_path,
+            "full_url": self.full_url,
         }
 
         if not public:
@@ -297,7 +302,7 @@ class User(BASE):
 
 
 class UserEmail(BASE):
-    """ Stores email information about the users.
+    """Stores email information about the users.
 
     Table -- user_emails
     """
@@ -323,7 +328,7 @@ class UserEmail(BASE):
 
 
 class UserEmailPending(BASE):
-    """ Stores email information about the users.
+    """Stores email information about the users.
 
     Table -- user_emails_pending
     """
@@ -353,7 +358,7 @@ class UserEmailPending(BASE):
 
 
 class Project(BASE):
-    """ Stores the projects.
+    """Stores the projects.
 
     Table -- projects
     """
@@ -487,6 +492,16 @@ class Project(BASE):
     )
 
     collaborator_groups = relation(
+        "PagureGroup",
+        secondary="projects_groups",
+        primaryjoin="projects.c.id==projects_groups.c.project_id",
+        secondaryjoin="and_(pagure_group.c.id==projects_groups.c.group_id,\
+                projects_groups.c.access=='collaborator')",
+        order_by="PagureGroup.group_name.asc()",
+        viewonly=True,
+    )
+
+    collaborator_project_groups = relation(
         "ProjectGroup",
         primaryjoin="and_(projects.c.id==projects_groups.c.project_id,\
                     projects_groups.c.access=='collaborator')",
@@ -509,28 +524,28 @@ class Project(BASE):
 
     @property
     def isa(self):
-        """ A string to allow finding out that this is a project. """
+        """A string to allow finding out that this is a project."""
         return "project"
 
     @property
     def mail_id(self):
-        """ Return a unique representation of the project as string that
+        """Return a unique representation of the project as string that
         can be used when sending emails.
         """
         return "%s-project-%s" % (self.fullname, self.id)
 
     @property
     def is_on_repospanner(self):
-        """ Returns whether this repo is on repoSpanner. """
+        """Returns whether this repo is on repoSpanner."""
         return self.repospanner_region is not None
 
     @property
     def path(self):
-        """ Return the name of the git repo on the filesystem. """
+        """Return the name of the git repo on the filesystem."""
         return "%s.git" % self.fullname
 
     def repospanner_repo_info(self, repotype, region=None):
-        """ Returns info for getting a repoSpanner repo for a project.
+        """Returns info for getting a repoSpanner repo for a project.
 
         Args:
             repotype (string): Type of repository
@@ -560,7 +575,7 @@ class Project(BASE):
         return url, regioninfo
 
     def _repospanner_repo_name(self, repotype, region=None):
-        """ Returns the name of a repo as named in repoSpanner.
+        """Returns the name of a repo as named in repoSpanner.
 
         Args:
             repotype (string): Type of repository
@@ -578,7 +593,7 @@ class Project(BASE):
         )
 
     def repopath(self, repotype):
-        """ Return the full repository path of the git repo on the filesystem.
+        """Return the full repository path of the git repo on the filesystem.
 
         If the repository is on repoSpanner, this will be a pseudo repository,
         which is "git repo enough" to be considered a valid repo, but any
@@ -620,7 +635,7 @@ class Project(BASE):
 
     @property
     def fullname(self):
-        """ Return the name of the git repo as user/project if it is a
+        """Return the name of the git repo as user/project if it is a
         project forked, otherwise it returns the project name.
         """
         str_name = self.name
@@ -632,7 +647,7 @@ class Project(BASE):
 
     @property
     def url_path(self):
-        """ Return the path at which this project can be accessed in the
+        """Return the path at which this project can be accessed in the
         web UI.
         """
         path = self.name
@@ -642,14 +657,20 @@ class Project(BASE):
             path = "fork/%s/%s" % (self.user.user, path)
         return path
 
+    @property
+    def full_url(self):
+        """Returns the default status of the board."""
+        base_url = pagure_config["APP_URL"].rstrip("/")
+        return "/".join([base_url, self.url_path])
+
     @property
     def tags_text(self):
-        """ Return the list of tags in a simple text form. """
+        """Return the list of tags in a simple text form."""
         return [tag.tag for tag in self.tags]
 
     @property
     def settings(self):
-        """ Return the dict stored as string in the database as an actual
+        """Return the dict stored as string in the database as an actual
         dict object.
         """
         default = {
@@ -693,12 +714,12 @@ class Project(BASE):
 
     @settings.setter
     def settings(self, settings):
-        """ Ensures the settings are properly saved. """
+        """Ensures the settings are properly saved."""
         self._settings = json.dumps(settings)
 
     @property
     def milestones(self):
-        """ Return the dict stored as string in the database as an actual
+        """Return the dict stored as string in the database as an actual
         dict object.
         """
         milestones = {}
@@ -722,13 +743,12 @@ class Project(BASE):
 
     @milestones.setter
     def milestones(self, milestones):
-        """ Ensures the milestones are properly saved. """
+        """Ensures the milestones are properly saved."""
         self._milestones = json.dumps(milestones)
 
     @property
     def milestones_keys(self):
-        """ Return the list of milestones so we can keep the order consistent.
-        """
+        """Return the list of milestones so we can keep the order consistent."""
         milestones_keys = {}
 
         if self._milestones_keys:
@@ -738,12 +758,12 @@ class Project(BASE):
 
     @milestones_keys.setter
     def milestones_keys(self, milestones_keys):
-        """ Ensures the milestones keys are properly saved. """
+        """Ensures the milestones keys are properly saved."""
         self._milestones_keys = json.dumps(milestones_keys)
 
     @property
     def priorities(self):
-        """ Return the dict stored as string in the database as an actual
+        """Return the dict stored as string in the database as an actual
         dict object.
         """
         priorities = {}
@@ -755,12 +775,12 @@ class Project(BASE):
 
     @priorities.setter
     def priorities(self, priorities):
-        """ Ensures the priorities are properly saved. """
+        """Ensures the priorities are properly saved."""
         self._priorities = json.dumps(priorities)
 
     @property
     def block_users(self):
-        """ Return the dict stored as string in the database as an actual
+        """Return the dict stored as string in the database as an actual
         dict object.
         """
         block_users = []
@@ -772,12 +792,12 @@ class Project(BASE):
 
     @block_users.setter
     def block_users(self, block_users):
-        """ Ensures the block_users are properly saved. """
+        """Ensures the block_users are properly saved."""
         self._block_users = json.dumps(block_users)
 
     @property
     def quick_replies(self):
-        """ Return a list of quick replies available for pull requests and
+        """Return a list of quick replies available for pull requests and
         issues.
         """
         quick_replies = []
@@ -789,12 +809,12 @@ class Project(BASE):
 
     @quick_replies.setter
     def quick_replies(self, quick_replies):
-        """ Ensures the quick replies are properly saved. """
+        """Ensures the quick replies are properly saved."""
         self._quick_replies = json.dumps(quick_replies)
 
     @property
     def notifications(self):
-        """ Return the dict stored as string in the database as an actual
+        """Return the dict stored as string in the database as an actual
         dict object.
         """
         notifications = {}
@@ -806,12 +826,12 @@ class Project(BASE):
 
     @notifications.setter
     def notifications(self, notifications):
-        """ Ensures the notifications are properly saved. """
+        """Ensures the notifications are properly saved."""
         self._notifications = json.dumps(notifications)
 
     @property
     def reports(self):
-        """ Return the dict stored as string in the database as an actual
+        """Return the dict stored as string in the database as an actual
         dict object.
         """
         reports = {}
@@ -823,12 +843,12 @@ class Project(BASE):
 
     @reports.setter
     def reports(self, reports):
-        """ Ensures the reports are properly saved. """
+        """Ensures the reports are properly saved."""
         self._reports = json.dumps(reports)
 
     @property
     def close_status(self):
-        """ Return the dict stored as string in the database as an actual
+        """Return the dict stored as string in the database as an actual
         dict object.
         """
         close_status = []
@@ -840,12 +860,12 @@ class Project(BASE):
 
     @close_status.setter
     def close_status(self, close_status):
-        """ Ensures the different close status are properly saved. """
+        """Ensures the different close status are properly saved."""
         self._close_status = json.dumps(close_status)
 
     @property
     def open_requests(self):
-        """ Returns the number of open pull-requests for this project. """
+        """Returns the number of open pull-requests for this project."""
         return (
             BASE.metadata.bind.query(PullRequest)
             .filter(self.id == PullRequest.project_id)
@@ -855,7 +875,7 @@ class Project(BASE):
 
     @property
     def open_tickets(self):
-        """ Returns the number of open tickets for this project. """
+        """Returns the number of open tickets for this project."""
         return (
             BASE.metadata.bind.query(Issue)
             .filter(self.id == Issue.project_id)
@@ -865,7 +885,7 @@ class Project(BASE):
 
     @property
     def open_tickets_public(self):
-        """ Returns the number of open tickets for this project. """
+        """Returns the number of open tickets for this project."""
         return (
             BASE.metadata.bind.query(Issue)
             .filter(self.id == Issue.project_id)
@@ -876,7 +896,7 @@ class Project(BASE):
 
     @property
     def contributors(self):
-        """ Return the dict presenting the different contributors of the
+        """Return the dict presenting the different contributors of the
         project based on their access level.
         """
         contributors = collections.defaultdict(list)
@@ -888,7 +908,7 @@ class Project(BASE):
 
     @property
     def contributor_groups(self):
-        """ Return the dict presenting the different contributors of the
+        """Return the dict presenting the different contributors of the
         project based on their access level.
         """
         contributors = collections.defaultdict(list)
@@ -899,7 +919,7 @@ class Project(BASE):
         return contributors
 
     def get_project_users(self, access, combine=True):
-        """ Returns the list of users/groups of the project according
+        """Returns the list of users/groups of the project according
         to the given access.
 
         :arg access: the access level to query for, can be: 'admin',
@@ -956,7 +976,7 @@ class Project(BASE):
                 return list(users - collaborators - committers - admins)
 
     def get_project_groups(self, access, combine=True):
-        """ Returns the list of groups of the project according
+        """Returns the list of groups of the project according
         to the given access.
 
         :arg access: the access level to query for, can be: 'admin',
@@ -1000,9 +1020,8 @@ class Project(BASE):
             elif access == "collaborator":
                 committers = set(self.committer_groups)
                 admins = set(self.admin_groups)
-                return list(
-                    set(self.collaborator_groups) - committers - admins
-                )
+                collaborators = set(self.collaborator_groups)
+                return list(collaborators - committers - admins)
             elif access == "ticket":
                 committers = set(self.committer_groups)
                 admins = set(self.admin_groups)
@@ -1012,8 +1031,7 @@ class Project(BASE):
 
     @property
     def access_users(self):
-        """ Return a dictionary with all user access
-        """
+        """Return a dictionary with all user access"""
         return {
             "admin": sorted(
                 self.get_project_users(access="admin", combine=False),
@@ -1055,8 +1073,7 @@ class Project(BASE):
 
     @property
     def access_groups(self):
-        """ Return a dictionary with all group access
-        """
+        """Return a dictionary with all group access"""
         return {
             "admin": sorted(
                 self.get_project_groups(access="admin", combine=False),
@@ -1078,17 +1095,15 @@ class Project(BASE):
 
     @property
     def active_boards(self):
-        """ Returns the list of active boards. """
+        """Returns the list of active boards."""
         return [board for board in self.boards if board.active]
 
     def lock(self, ltype):
-        """ Get a SQL lock of type ltype for the current project.
-        """
+        """Get a SQL lock of type ltype for the current project."""
         return ProjectLocker(self, ltype)
 
     def to_json(self, public=False, api=False):
-        """ Return a representation of the project as JSON.
-        """
+        """Return a representation of the project as JSON."""
         custom_keys = [[key.name, key.key_type] for key in self.issue_keys]
 
         output = {
@@ -1096,6 +1111,7 @@ class Project(BASE):
             "name": self.name,
             "fullname": self.fullname,
             "url_path": self.url_path,
+            "full_url": self.full_url,
             "description": self.description,
             "namespace": self.namespace,
             "parent": self.parent.to_json(public=public, api=api)
@@ -1120,7 +1136,7 @@ class Project(BASE):
 
 
 class ProjectLock(BASE):
-    """ Table used to define project-specific locks.
+    """Table used to define project-specific locks.
 
     Table -- project_locks
     """
@@ -1143,7 +1159,7 @@ class ProjectLock(BASE):
 
 
 class ProjectLocker(object):
-    """ This is used as a context manager to lock a project.
+    """This is used as a context manager to lock a project.
 
     This is used as a context manager to make it very explicit when we unlock
     the project, and so that we unlock even if an exception occurs.
@@ -1186,7 +1202,7 @@ class ProjectLocker(object):
 
 
 class ProjectUser(BASE):
-    """ Stores the user of a projects.
+    """Stores the user of a projects.
 
     Table -- user_projects
     """
@@ -1213,7 +1229,10 @@ class ProjectUser(BASE):
         ),
         nullable=False,
     )
-    branches = sa.Column(sa.Text, nullable=True,)
+    branches = sa.Column(
+        sa.Text,
+        nullable=True,
+    )
 
     project = relation(
         "Project",
@@ -1227,7 +1246,7 @@ class ProjectUser(BASE):
 
 
 class SSHKey(BASE):
-    """ Stores information about SSH keys.
+    """Stores information about SSH keys.
 
     Every instance needs to either have user_id set (SSH key for a specific
     user) or project_id ("deploy key" for a specific project).
@@ -1270,14 +1289,14 @@ class SSHKey(BASE):
     # assigned to a Project or a User, but not both.
     @validates("project_id")
     def validate_project_id(self, key, value):
-        """ Validates that user_id is not set. """
+        """Validates that user_id is not set."""
         if self.user_id is not None:
             raise ValueError("SSHKey can't have both project and user")
         return value
 
     @validates("user_id")
     def validate_user_id(self, key, value):
-        """ Validates that project_id is not set. """
+        """Validates that project_id is not set."""
         if self.project_id is not None:
             raise ValueError("SSHKey can't have both user and project")
         return value
@@ -1307,7 +1326,7 @@ class SSHKey(BASE):
 
 
 class Issue(BASE):
-    """ Stores the issues reported on a project.
+    """Stores the issues reported on a project.
 
     Table -- issues
     """
@@ -1409,12 +1428,12 @@ class Issue(BASE):
 
     @property
     def attachments(self):
-        """ Return a list of attachment tuples: (LINK, FILENAME, DISPLAY_NAME,
-        DATE) """
+        """Return a list of attachment tuples: (LINK, FILENAME, DISPLAY_NAME,
+        DATE)"""
 
         def extract_info(text):
-            """ Return a tuple containing the link, file name, and the
-            "display" file name from the markdown attachment link """
+            """Return a tuple containing the link, file name, and the
+            "display" file name from the markdown attachment link"""
             pattern_md = re.compile(r"^\[\!(.*)\]")
             pattern_link = re.compile(r"\(([^)]+)\)")
             pattern_file = re.compile(r"\[([^]]+)\]")
@@ -1478,63 +1497,67 @@ class Issue(BASE):
 
     @property
     def isa(self):
-        """ A string to allow finding out that this is an issue. """
+        """A string to allow finding out that this is an issue."""
         return "issue"
 
     @property
     def repotype(self):
-        """ A string returning the repotype for repopath() calls. """
+        """A string returning the repotype for repopath() calls."""
         return "tickets"
 
     @property
     def mail_id(self):
-        """ Return a unique reprensetation of the issue as string that
+        """Return a unique reprensetation of the issue as string that
         can be used when sending emails.
         """
         return "%s-ticket-%s" % (self.project.name, self.uid)
 
     @property
     def tags_text(self):
-        """ Return the list of tags in a simple text form. """
-        return [tag.tag for tag in self.tags]
+        """Return the list of tags in a simple text form."""
+        return sorted([tag.tag for tag in self.tags])
 
     @property
     def depending_text(self):
-        """ Return the list of issue this issue depends on in simple text. """
+        """Return the list of issue this issue depends on in simple text."""
         return [issue.id for issue in self.parents]
 
     @property
     def blocking_text(self):
-        """ Return the list of issue this issue blocks on in simple text. """
+        """Return the list of issue this issue blocks on in simple text."""
         return [issue.id for issue in self.children]
 
     @property
     def user_comments(self):
-        """ Return user comments only, filter it from notifications
-        """
+        """Return user comments only, filter it from notifications"""
         return [
             comment for comment in self.comments if not comment.notification
         ]
 
     @property
     def sortable_priority(self):
-        """ Return an empty string if no priority is set allowing issues to
-        be sorted using this attribute. """
+        """Return an empty string if no priority is set allowing issues to
+        be sorted using this attribute."""
         return self.priority if self.priority else ""
 
     @property
     def boards_name(self):
-        """ Return the list of boards the issue is part of
-        """
+        """Return the list of boards the issue is part of"""
         out = []
         for status_board in self.boards_issues:
             out.append(status_board.board.name)
         return out
 
-    def to_json(self, public=False, with_comments=True, with_project=False):
-        """ Returns a dictionary representation of the issue.
+    @property
+    def full_url(self):
+        """Returns the default status of the board."""
+        base_url = pagure_config["APP_URL"].rstrip("/")
+        return "/".join(
+            [base_url, self.project.url_path, "issue", str(self.id)]
+        )
 
-        """
+    def to_json(self, public=False, with_comments=True, with_project=False):
+        """Returns a dictionary representation of the issue."""
         custom_fields = [
             dict(
                 name=field.key.name,
@@ -1573,6 +1596,7 @@ class Issue(BASE):
             ]
             if self.related_prs
             else [],
+            "full_url": self.full_url,
         }
 
         comments = []
@@ -1594,7 +1618,7 @@ class Issue(BASE):
 
 
 class IssueToIssue(BASE):
-    """ Stores the parent/child relationship between two issues.
+    """Stores the parent/child relationship between two issues.
 
     Table -- issue_to_issue
     """
@@ -1614,7 +1638,7 @@ class IssueToIssue(BASE):
 
 
 class PrToIssue(BASE):
-    """ Stores the associations between issues and pull-requests.
+    """Stores the associations between issues and pull-requests.
 
     Table -- pr_to_issue
     """
@@ -1637,7 +1661,7 @@ class PrToIssue(BASE):
 
 
 class IssueComment(BASE):
-    """ Stores the comments made on a commit/file.
+    """Stores the comments made on a commit/file.
 
     Table -- issue_comments
     """
@@ -1697,7 +1721,7 @@ class IssueComment(BASE):
 
     @property
     def mail_id(self):
-        """ Return a unique reprensetation of the issue as string that
+        """Return a unique reprensetation of the issue as string that
         can be used when sending emails.
         """
         return "%s-ticket-%s-%s" % (
@@ -1708,12 +1732,12 @@ class IssueComment(BASE):
 
     @property
     def parent(self):
-        """ Return the parent, in this case the issue object. """
+        """Return the parent, in this case the issue object."""
         return self.issue
 
     @property
     def reactions(self):
-        """ Return the reactions stored as a string in the database parsed as
+        """Return the reactions stored as a string in the database parsed as
         an actual dict object.
         """
         if self._reactions:
@@ -1722,13 +1746,11 @@ class IssueComment(BASE):
 
     @reactions.setter
     def reactions(self, reactions):
-        """ Ensures that reactions are properly saved. """
+        """Ensures that reactions are properly saved."""
         self._reactions = json.dumps(reactions)
 
     def to_json(self, public=False):
-        """ Returns a dictionary representation of the issue.
-
-        """
+        """Returns a dictionary representation of the issue."""
         output = {
             "id": self.id,
             "comment": self.comment,
@@ -1746,7 +1768,7 @@ class IssueComment(BASE):
 
 
 class IssueKeys(BASE):
-    """ Stores the custom keys a project can use on issues.
+    """Stores the custom keys a project can use on issues.
 
     Table -- issue_keys
     """
@@ -1781,7 +1803,7 @@ class IssueKeys(BASE):
 
     @property
     def data(self):
-        """ Return the list of items """
+        """Return the list of items"""
         if self.key_data:
             return json.loads(self.key_data)
         else:
@@ -1789,7 +1811,7 @@ class IssueKeys(BASE):
 
     @data.setter
     def data(self, data_obj):
-        """ Store the list data in JSON. """
+        """Store the list data in JSON."""
         if data_obj is None:
             self.key_data = None
         else:
@@ -1797,7 +1819,7 @@ class IssueKeys(BASE):
 
 
 class IssueValues(BASE):
-    """ Stores the values of the custom keys set by project on issues.
+    """Stores the values of the custom keys set by project on issues.
 
     Table -- issue_values
     """
@@ -1834,7 +1856,7 @@ class IssueValues(BASE):
 
 
 class Tag(BASE):
-    """ Stores the tags.
+    """Stores the tags.
 
     Table -- tags
     """
@@ -1848,7 +1870,7 @@ class Tag(BASE):
 
 
 class TagIssue(BASE):
-    """ Stores the tag associated with an issue.
+    """Stores the tag associated with an issue.
 
     Table -- tags_issues
     """
@@ -1883,7 +1905,7 @@ class TagIssue(BASE):
 
 
 class TagColored(BASE):
-    """ Stores the colored tags.
+    """Stores the colored tags.
 
     Table -- tags_colored
     """
@@ -1932,7 +1954,7 @@ class TagColored(BASE):
 
 
 class TagIssueColored(BASE):
-    """ Stores the colored tag associated with an issue.
+    """Stores the colored tag associated with an issue.
 
     Table -- tags_issues_colored
     """
@@ -1976,7 +1998,7 @@ class TagIssueColored(BASE):
 
 
 class TagProject(BASE):
-    """ Stores the tag associated with a project.
+    """Stores the tag associated with a project.
 
     Table -- tags_projects
     """
@@ -2014,7 +2036,7 @@ class TagProject(BASE):
 
 
 class PullRequest(BASE):
-    """ Stores the pull requests created on a project.
+    """Stores the pull requests created on a project.
 
     Table -- pull_requests
     """
@@ -2153,37 +2175,36 @@ class PullRequest(BASE):
 
     @property
     def isa(self):
-        """ A string to allow finding out that this is an pull-request. """
+        """A string to allow finding out that this is an pull-request."""
         return "pull-request"
 
     @property
     def repotype(self):
-        """ A string returning the repotype for repopath() calls. """
+        """A string returning the repotype for repopath() calls."""
         return "requests"
 
     @property
     def mail_id(self):
-        """ Return a unique reprensetation of the issue as string that
+        """Return a unique reprensetation of the issue as string that
         can be used when sending emails.
         """
         return "%s-pull-request-%s" % (self.project.name, self.uid)
 
     @property
     def tags_text(self):
-        """ Return the list of tags in a simple text form. """
-        return [tag.tag for tag in self.tags]
+        """Return the list of tags in a simple text form."""
+        return sorted([tag.tag for tag in self.tags])
 
     @property
     def discussion(self):
-        """ Return the list of comments related to the pull-request itself,
+        """Return the list of comments related to the pull-request itself,
         ie: not related to a specific commit.
         """
         return [comment for comment in self.comments if not comment.commit_id]
 
     @property
     def flags_stats(self):
-        """ Return some stats about the flags associated with this PR.
-        """
+        """Return some stats about the flags associated with this PR."""
         flags = self.flags
         flags.reverse()
 
@@ -2200,7 +2221,7 @@ class PullRequest(BASE):
 
     @property
     def score(self):
-        """ Return the review score of the pull-request by checking the
+        """Return the review score of the pull-request by checking the
         number of +1, -1, :thumbup: and :thumbdown: in the comment of the
         pull-request.
         This includes only the main comments not the inline ones.
@@ -2222,7 +2243,7 @@ class PullRequest(BASE):
 
     @property
     def threshold_reached(self):
-        """ Return whether the pull-request has reached the threshold above
+        """Return whether the pull-request has reached the threshold above
         which it is allowed to be merged, if the project requests a minimal
         score on pull-request, otherwise returns None.
 
@@ -2237,23 +2258,28 @@ class PullRequest(BASE):
 
     @property
     def remote(self):
-        """ Return whether the current PullRequest is a remote pull-request
+        """Return whether the current PullRequest is a remote pull-request
         or not.
         """
         return self.remote_git is not None
 
     @property
     def user_comments(self):
-        """ Return user comments only, filter it from notifications
-        """
+        """Return user comments only, filter it from notifications"""
         return [
             comment for comment in self.comments if not comment.notification
         ]
 
-    def to_json(self, public=False, api=False, with_comments=True):
-        """ Returns a dictionary representation of the pull-request.
+    @property
+    def full_url(self):
+        """Returns the default status of the board."""
+        base_url = pagure_config["APP_URL"].rstrip("/")
+        return "/".join(
+            [base_url, self.project.url_path, "pull-request", str(self.id)]
+        )
 
-        """
+    def to_json(self, public=False, api=False, with_comments=True):
+        """Returns a dictionary representation of the pull-request."""
         output = {
             "id": self.id,
             "uid": self.uid,
@@ -2283,6 +2309,7 @@ class PullRequest(BASE):
             "cached_merge_status": self.merge_status or "unknown",
             "threshold_reached": self.threshold_reached,
             "tags": self.tags_text,
+            "full_url": self.full_url,
         }
 
         comments = []
@@ -2296,7 +2323,7 @@ class PullRequest(BASE):
 
 
 class PullRequestComment(BASE):
-    """ Stores the comments made on a pull-request.
+    """Stores the comments made on a pull-request.
 
     Table -- pull_request_comments
     """
@@ -2364,7 +2391,7 @@ class PullRequestComment(BASE):
 
     @property
     def mail_id(self):
-        """ Return a unique representation of the issue as string that
+        """Return a unique representation of the issue as string that
         can be used when sending emails.
         """
         return "%s-pull-request-%s-%s" % (
@@ -2375,12 +2402,12 @@ class PullRequestComment(BASE):
 
     @property
     def parent(self):
-        """ Return the parent, in this case the pull_request object. """
+        """Return the parent, in this case the pull_request object."""
         return self.pull_request
 
     @property
     def reactions(self):
-        """ Return the reactions stored as a string in the database parsed as
+        """Return the reactions stored as a string in the database parsed as
         an actual dict object.
         """
         if self._reactions:
@@ -2389,11 +2416,11 @@ class PullRequestComment(BASE):
 
     @reactions.setter
     def reactions(self, reactions):
-        """ Ensures that reactions are properly saved. """
+        """Ensures that reactions are properly saved."""
         self._reactions = json.dumps(reactions)
 
     def to_json(self, public=False):
-        """ Return a dict representation of the pull-request comment. """
+        """Return a dict representation of the pull-request comment."""
 
         return {
             "id": self.id,
@@ -2415,7 +2442,7 @@ class PullRequestComment(BASE):
 
 
 class PullRequestFlag(BASE):
-    """ Stores the flags attached to a pull-request.
+    """Stores the flags attached to a pull-request.
 
     Table -- pull_request_flags
     """
@@ -2482,7 +2509,7 @@ class PullRequestFlag(BASE):
 
     @property
     def mail_id(self):
-        """ Return a unique representation of the flag as string that
+        """Return a unique representation of the flag as string that
         can be used when sending emails.
         """
         return "%s-pull-request-%s-%s" % (
@@ -2492,9 +2519,7 @@ class PullRequestFlag(BASE):
         )
 
     def to_json(self, public=False):
-        """ Returns a dictionary representation of the pull-request.
-
-        """
+        """Returns a dictionary representation of the pull-request."""
         output = {
             "pull_request_uid": self.pull_request_uid,
             "username": self.username,
@@ -2511,7 +2536,7 @@ class PullRequestFlag(BASE):
 
 
 class CommitFlag(BASE):
-    """ Stores the flags attached to a commit.
+    """Stores the flags attached to a commit.
 
     Table -- commit_flags
     """
@@ -2527,7 +2552,9 @@ class CommitFlag(BASE):
         index=True,
     )
     token_id = sa.Column(
-        sa.String(64), sa.ForeignKey("tokens.id"), nullable=False
+        sa.String(64),
+        sa.ForeignKey("tokens.id", ondelete="CASCADE", onupdate="CASCADE"),
+        nullable=True,
     )
     user_id = sa.Column(
         sa.Integer,
@@ -2573,12 +2600,12 @@ class CommitFlag(BASE):
 
     @property
     def isa(self):
-        """ A string to allow finding out that this is a commit flag. """
+        """A string to allow finding out that this is a commit flag."""
         return "commit-flag"
 
     @property
     def mail_id(self):
-        """ Return a unique representation of the flag as string that
+        """Return a unique representation of the flag as string that
         can be used when sending emails.
         """
         return "%s-commit-%s-%s" % (
@@ -2588,9 +2615,7 @@ class CommitFlag(BASE):
         )
 
     def to_json(self, public=False):
-        """ Returns a dictionary representation of the commit flag.
-
-        """
+        """Returns a dictionary representation of the commit flag."""
         output = {
             "commit_hash": self.commit_hash,
             "username": self.username,
@@ -2607,7 +2632,7 @@ class CommitFlag(BASE):
 
 
 class TagPullRequest(BASE):
-    """ Stores the tag associated with an pull-request.
+    """Stores the tag associated with an pull-request.
 
     Table -- tags_pull_requests
     """
@@ -2664,7 +2689,7 @@ class PagureGroupType(BASE):
     )
 
     def __repr__(self):
-        """ Return a string representation of this object. """
+        """Return a string representation of this object."""
 
         return "GroupType: %s" % (self.group_type)
 
@@ -2706,14 +2731,18 @@ class PagureGroup(BASE):
     )
 
     def __repr__(self):
-        """ Return a string representation of this object. """
+        """Return a string representation of this object."""
 
         return "Group: %s - name %s" % (self.id, self.group_name)
 
-    def to_json(self, public=False):
-        """ Returns a dictionary representation of the pull-request.
+    @property
+    def full_url(self):
+        """Returns the default status of the board."""
+        base_url = pagure_config["APP_URL"].rstrip("/")
+        return "/".join([base_url, "group", self.group_name])
 
-        """
+    def to_json(self, public=False):
+        """Returns a dictionary representation of the pull-request."""
         output = {
             "name": self.group_name,
             "display_name": self.display_name,
@@ -2721,6 +2750,7 @@ class PagureGroup(BASE):
             "group_type": self.group_type,
             "creator": self.creator.to_json(public=public),
             "date_created": arrow_ts(self.created),
+            "full_url": self.full_url,
             "members": [user.username for user in self.users],
         }
 
@@ -2750,7 +2780,10 @@ class ProjectGroup(BASE):
         ),
         nullable=False,
     )
-    branches = sa.Column(sa.Text, nullable=True,)
+    branches = sa.Column(
+        sa.Text,
+        nullable=True,
+    )
 
     project = relation(
         "Project",
@@ -2770,7 +2803,7 @@ class ProjectGroup(BASE):
 
 
 class Star(BASE):
-    """ Stores users association with the all the projects which
+    """Stores users association with the all the projects which
     they have starred
 
     Table -- star
@@ -2812,7 +2845,7 @@ class Star(BASE):
 
 
 class Watcher(BASE):
-    """ Stores the user of a projects.
+    """Stores the user of a projects.
 
     Table -- watchers
     """
@@ -2920,9 +2953,7 @@ class PagureLog(BASE):
     )
 
     def to_json(self, public=False):
-        """ Returns a dictionary representation of the issue.
-
-        """
+        """Returns a dictionary representation of the issue."""
         output = {
             "id": self.id,
             "type": self.log_type,
@@ -2934,7 +2965,7 @@ class PagureLog(BASE):
         return output
 
     def __str__(self):
-        """ A string representation of this log entry. """
+        """A string representation of this log entry."""
         verb = ""
         desc = "%(user)s %(verb)s %(project)s#%(obj_id)s"
         arg = {
@@ -2989,7 +3020,7 @@ class PagureLog(BASE):
 
 
 class IssueWatcher(BASE):
-    """ Stores the users watching issues.
+    """Stores the users watching issues.
 
     Table -- issue_watchers
     """
@@ -3027,7 +3058,7 @@ class IssueWatcher(BASE):
 
 
 class PullRequestWatcher(BASE):
-    """ Stores the users watching issues.
+    """Stores the users watching issues.
 
     Table -- pull_request_watchers
     """
@@ -3086,7 +3117,7 @@ class ACL(BASE):
     )
 
     def __repr__(self):
-        """ Return a string representation of this object. """
+        """Return a string representation of this object."""
 
         return "ACL: %s - name %s" % (self.id, self.name)
 
@@ -3145,7 +3176,7 @@ class Token(BASE):
     )
 
     def __repr__(self):
-        """ Return a string representation of this object. """
+        """Return a string representation of this object."""
 
         return "Token: %s - name %s - expiration: %s" % (
             self.id,
@@ -3155,7 +3186,7 @@ class Token(BASE):
 
     @property
     def expired(self):
-        """ Returns whether a token has expired or not. """
+        """Returns whether a token has expired or not."""
         if datetime.datetime.utcnow().date() >= self.expiration.date():
             return True
         else:
@@ -3163,8 +3194,7 @@ class Token(BASE):
 
     @property
     def acls_list(self):
-        """ Return a list containing the name of each ACLs this token has.
-        """
+        """Return a list containing the name of each ACLs this token has."""
         return sorted(["%s" % acl.name for acl in self.acls])
 
     @property
@@ -3257,7 +3287,7 @@ class Board(BASE):
 
     @property
     def default_status(self):
-        """ Returns the default status of the board. """
+        """Returns the default status of the board."""
         out = None
         for status in self.statuses:
             if status.default:
@@ -3265,18 +3295,28 @@ class Board(BASE):
                 break
         return out
 
+    @property
+    def full_url(self):
+        """Returns the default status of the board."""
+        base_url = pagure_config["APP_URL"].rstrip("/")
+        return "/".join([base_url, self.project.url_path, "boards", self.name])
+
     def __repr__(self):
-        """ Return a string representation of this object. """
+        """Return a string representation of this object."""
 
-        return "Board: %s - name %s" % (self.id, self.name,)
+        return "Board: %s - name %s" % (
+            self.id,
+            self.name,
+        )
 
     def to_json(self):
-        """ The JSON representation of a board. """
+        """The JSON representation of a board."""
         return {
             "name": self.name,
             "active": self.active,
             "status": [status.to_json() for status in self.statuses],
             "tag": self.tag.to_json(),
+            "full_url": self.full_url,
         }
 
 
@@ -3322,7 +3362,7 @@ class BoardStatus(BASE):
     )
 
     def __repr__(self):
-        """ Return a string representation of this object. """
+        """Return a string representation of this object."""
 
         return "BoardStatus: %s - board: %s - name %s" % (
             self.id,
@@ -3331,7 +3371,7 @@ class BoardStatus(BASE):
         )
 
     def visible_tickets(self, watching_user):
-        """ Returns the sorted list of tickets visible to the user currently
+        """Returns the sorted list of tickets visible to the user currently
         watching.
 
         If the user currently watching (ie: ``watching_user``) is False, do not
@@ -3353,7 +3393,7 @@ class BoardStatus(BASE):
         ]
 
     def to_json(self):
-        """ The JSON representation of these objects. """
+        """The JSON representation of these objects."""
         return {
             "name": self.name,
             "bg_color": self.bg_color,
@@ -3420,7 +3460,7 @@ class BoardIssues(BASE):
     )
 
     def to_json(self):
-        """ The JSON representation of these objects. """
+        """The JSON representation of these objects."""
         return {
             "board": self.board.to_json() if self.board else None,
             "status": self.status.to_json(),
diff --git a/pagure/lib/model_base.py b/pagure/lib/model_base.py
index 1c7bdef..4aaa00e 100644
--- a/pagure/lib/model_base.py
+++ b/pagure/lib/model_base.py
@@ -8,13 +8,11 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import sqlalchemy
 from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import sessionmaker
-from sqlalchemy.orm import scoped_session
-
+from sqlalchemy.orm import scoped_session, sessionmaker
 
 CONVENTION = {
     "ix": "ix_%(table_name)s_%(column_0_label)s",
@@ -34,7 +32,7 @@ SESSIONMAKER = None
 
 
 def create_session(db_url=None, debug=False, pool_recycle=3600):
-    """ Create the Session object to use to query the database.
+    """Create the Session object to use to query the database.
 
     :arg db_url: URL used to connect to the database. The URL contains
     information with regards to the database engine, the host to connect
@@ -48,7 +46,7 @@ def create_session(db_url=None, debug=False, pool_recycle=3600):
     global SESSIONMAKER
 
     if SESSIONMAKER is None or (
-        db_url and db_url != ("%s" % SESSIONMAKER.kw["bind"].engine.url)
+        db_url and db_url != ("{}".format(SESSIONMAKER.kw["bind"].engine.url))
     ):
         if db_url is None:
             raise ValueError("First call to create_session needs db_url")
@@ -68,7 +66,7 @@ def create_session(db_url=None, debug=False, pool_recycle=3600):
             # Ignore the warning about con_record
             # pylint: disable=unused-argument
             def _fk_pragma_on_connect(dbapi_con, _):  # pragma: no cover
-                """ Tries to enforce referential constraints on sqlite. """
+                """Tries to enforce referential constraints on sqlite."""
                 dbapi_con.execute("pragma foreign_keys=ON")
 
             sqlalchemy.event.listen(engine, "connect", _fk_pragma_on_connect)
diff --git a/pagure/lib/notify.py b/pagure/lib/notify.py
index 0c124db..53b1eaa 100644
--- a/pagure/lib/notify.py
+++ b/pagure/lib/notify.py
@@ -8,12 +8,7 @@
 
 pagure notifications.
 """
-from __future__ import print_function, unicode_literals, absolute_import
-
-
-# pylint: disable=too-many-branches
-# pylint: disable=too-many-arguments
-
+from __future__ import absolute_import, print_function, unicode_literals
 
 import datetime
 import hashlib
@@ -22,20 +17,25 @@ import logging
 import os
 import re
 import smtplib
-import time
-import six
 import ssl
+import time
 from email.header import Header
 from email.mime.text import MIMEText
-from six.moves.urllib_parse import urljoin
 
 import blinker
 import flask
+import six
+from markdown.extensions.fenced_code import FencedBlockPreprocessor
+from six.moves.urllib_parse import urljoin
+
 import pagure.lib.query
 import pagure.lib.tasks_services
 from pagure.config import config as pagure_config
 from pagure.pfmarkdown import MENTION_RE
 
+# pylint: disable=too-many-branches
+# pylint: disable=too-many-arguments
+
 
 _log = logging.getLogger(__name__)
 
@@ -46,7 +46,7 @@ if pagure_config["EVENTSOURCE_SOURCE"]:
 
 
 def fedmsg_publish(*args, **kwargs):  # pragma: no cover
-    """ Try to publish a message on the fedmsg bus. """
+    """Try to publish a message on the fedmsg bus."""
     if not pagure_config.get("FEDMSG_NOTIFICATIONS", True):
         return
 
@@ -72,20 +72,35 @@ def fedmsg_publish(*args, **kwargs):  # pragma: no cover
 
 
 def fedora_messaging_publish(topic, message):  # pragma: no cover
-    """ Try to publish a message on AMQP using fedora-messaging. """
+    """Try to publish a message on AMQP using fedora-messaging."""
     if not pagure_config.get("FEDORA_MESSAGING_NOTIFICATIONS", False):
         return
 
     try:
         import fedora_messaging.api
         import fedora_messaging.exceptions
+        import pagure_messages
 
-        msg = fedora_messaging.api.Message(
-            topic="pagure.{}".format(topic), body=message
+        msg_cls = pagure_messages.get_message_object_from_topic(
+            "pagure.{}".format(topic)
         )
+
+        if not hasattr(msg_cls, "app_name") is False:
+            _log.warning(
+                "pagure is about to send a message that has no schemas: "
+                "pagure.%s",
+                topic,
+            )
+
+        msg = msg_cls(body=message)
+        if not msg.topic:
+            msg.topic = "pagure.{}".format(topic)
         fedora_messaging.api.publish(msg)
     except ImportError:
-        _log.warning("Fedora messaging does not appear to be available")
+        _log.warning(
+            "Fedora messaging or pagure-messages does not appear to be "
+            "available"
+        )
     except fedora_messaging.exceptions.PublishReturned as e:
         _log.warning(
             "Fedora Messaging broker rejected message %s: %s", msg.id, e
@@ -100,7 +115,7 @@ stomp_conn = None
 
 
 def stomp_publish(topic, message):
-    """ Try to publish a message on a Stomp-compliant message bus. """
+    """Try to publish a message on a Stomp-compliant message bus."""
     if not pagure_config.get("STOMP_NOTIFICATIONS", False):
         return
     # We catch Exception if we want :-p
@@ -140,12 +155,12 @@ def blinker_publish(topic, message):
 
 
 def mqtt_publish(topic, message):
-    """ Try to publish a message on a MQTT message bus. """
+    """Try to publish a message on a MQTT message bus."""
     if not pagure_config.get("MQTT_NOTIFICATIONS", False):
         return
 
     mqtt_host = pagure_config.get("MQTT_HOST")
-    mqtt_port = pagure_config.get("MQTT_PORT")
+    mqtt_port = int(pagure_config.get("MQTT_PORT"))
 
     mqtt_username = pagure_config.get("MQTT_USERNAME")
     mqtt_pass = pagure_config.get("MQTT_PASSWORD")
@@ -191,7 +206,7 @@ def mqtt_publish(topic, message):
 
 
 def log(project, topic, msg, webhook=True):
-    """ This is the place where we send notifications to user about actions
+    """This is the place where we send notifications to user about actions
     occuring in pagure.
     """
 
@@ -231,10 +246,13 @@ def log(project, topic, msg, webhook=True):
 
 
 def _add_mentioned_users(emails, comment):
-    """ Check the comment to see if an user is mentioned in it and if
+    """Check the comment to see if an user is mentioned in it and if
     so add this user to the list of people to notify.
     """
-    for username in re.findall(MENTION_RE, comment):
+    filtered_comment = re.sub(
+        FencedBlockPreprocessor.FENCED_BLOCK_RE, "", comment
+    )
+    for username in re.findall(MENTION_RE, filtered_comment):
         user = pagure.lib.query.search_user(flask.g.session, username=username)
         if user:
             emails.add(user.default_email)
@@ -242,7 +260,7 @@ def _add_mentioned_users(emails, comment):
 
 
 def _clean_emails(emails, user):
-    """ Remove the email of the user doing the action if it is in the list.
+    """Remove the email of the user doing the action if it is in the list.
 
     This avoids receiving emails about action you do.
     """
@@ -260,7 +278,7 @@ def _clean_emails(emails, user):
 
 
 def _get_emails_for_obj(obj):
-    """ Return the list of emails to send notification to when notifying
+    """Return the list of emails to send notification to when notifying
     about the specified issue or pull-request.
     """
     emails = set()
@@ -358,7 +376,7 @@ def _get_emails_for_commit_notification(project):
 
 
 def _build_url(*args):
-    """ Build a URL from a given list of arguments. """
+    """Build a URL from a given list of arguments."""
     items = []
     for idx, arg in enumerate(args):
         arg = "%s" % arg
@@ -372,7 +390,7 @@ def _build_url(*args):
 
 
 def _fullname_to_url(fullname):
-    """ For forked projects, fullname is 'forks/user/...' but URL is
+    """For forked projects, fullname is 'forks/user/...' but URL is
     'fork/user/...'. This is why we can't have nice things.
     """
     if fullname.startswith("forks/"):
@@ -391,7 +409,7 @@ def send_email(
     reporter=None,
     assignee=None,
 ):  # pragma: no cover
-    """ Send an email with the specified information.
+    """Send an email with the specified information.
 
     :arg text: the content of the email to send
     :type text: unicode
@@ -511,7 +529,9 @@ def send_email(
                 keyfile = pagure_config.get("SMTP_KEYFILE") or None
                 certfile = pagure_config.get("SMTP_CERTFILE") or None
                 respcode, _ = smtp.starttls(
-                    keyfile=keyfile, certfile=certfile, context=context,
+                    keyfile=keyfile,
+                    certfile=certfile,
+                    context=context,
                 )
                 if respcode != 220:
                     _log.warning(
@@ -537,7 +557,7 @@ def send_email(
 
 
 def notify_new_comment(comment, user=None):
-    """ Notify the people following an issue that a new comment was added
+    """Notify the people following an issue that a new comment was added
     to the issue.
     """
 
@@ -583,7 +603,7 @@ def notify_new_comment(comment, user=None):
 
 
 def notify_new_issue(issue, user=None):
-    """ Notify the people following a project that a new issue was added
+    """Notify the people following a project that a new issue was added
     to it.
     """
     text = """
@@ -625,8 +645,7 @@ def notify_new_issue(issue, user=None):
 
 
 def notify_assigned_issue(issue, new_assignee, user):
-    """ Notify the people following an issue that the assignee changed.
-    """
+    """Notify the people following an issue that the assignee changed."""
     action = "reset"
     if new_assignee:
         action = "assigned to `%s`" % new_assignee.user
@@ -670,8 +689,7 @@ The issue: `%s` of project: `%s` has been %s by %s.
 
 
 def notify_status_change_issue(issue, user):
-    """ Notify the people following a project that an issue changed status.
-    """
+    """Notify the people following a project that an issue changed status."""
     status = issue.status
     if status.lower() != "open" and issue.close_status:
         status = "%s as %s" % (status, issue.close_status)
@@ -711,8 +729,7 @@ The status of the issue: `%s` of project: `%s` has been updated to: %s by %s.
 
 
 def notify_meta_change_issue(issue, user, msg):
-    """ Notify that a custom field changed
-    """
+    """Notify that a custom field changed"""
     text = """
 `%s` updated issue.
 
@@ -748,8 +765,7 @@ def notify_meta_change_issue(issue, user, msg):
 
 
 def notify_assigned_request(request, new_assignee, user):
-    """ Notify the people following a pull-request that the assignee changed.
-    """
+    """Notify the people following a pull-request that the assignee changed."""
     action = "reset"
     if new_assignee:
         action = "assigned to `%s`" % new_assignee.user
@@ -793,7 +809,7 @@ The pull-request: `%s` of project: `%s` has been %s by %s.
 
 
 def notify_new_pull_request(request):
-    """ Notify the people following a project that a new pull-request was
+    """Notify the people following a project that a new pull-request was
     added to it.
     """
     text = """
@@ -833,7 +849,7 @@ def notify_new_pull_request(request):
 
 
 def notify_merge_pull_request(request, user):
-    """ Notify the people following a project that a pull-request was merged
+    """Notify the people following a project that a pull-request was merged
     in it.
     """
     text = """
@@ -877,7 +893,7 @@ Merged pull-request:
 
 
 def notify_reopen_pull_request(request, user):
-    """ Notify the people following a project that a closed pull-request
+    """Notify the people following a project that a closed pull-request
     has been reopened.
     """
     text = """
@@ -921,7 +937,7 @@ Reopened pull-request:
 
 
 def notify_closed_pull_request(request, user):
-    """ Notify the people following a project that a pull-request was
+    """Notify the people following a project that a pull-request was
     closed in it.
     """
     text = """
@@ -966,7 +982,7 @@ Closed pull-request:
 
 
 def notify_pull_request_comment(comment, user):
-    """ Notify the people following a pull-request that a new comment was
+    """Notify the people following a pull-request that a new comment was
     added to it.
     """
     text = """
@@ -1012,8 +1028,8 @@ def notify_pull_request_comment(comment, user):
     )
 
 
-def notify_pull_request_flag(flag, user):
-    """ Notify the people following a pull-request that a new flag was
+def notify_pull_request_flag(flag, request, user):
+    """Notify the people following a pull-request that a new flag was
     added to it.
     """
     text = """
@@ -1022,38 +1038,35 @@ def notify_pull_request_flag(flag, user):
 %s
 """ % (
         flag.username,
-        flag.pull_request.title,
+        request.title,
         flag.status,
         flag.comment,
         _build_url(
             pagure_config["APP_URL"],
-            _fullname_to_url(flag.pull_request.project.fullname),
+            _fullname_to_url(request.project.fullname),
             "pull-request",
-            flag.pull_request.id,
+            request.id,
         ),
     )
-    mail_to = _get_emails_for_obj(flag.pull_request)
+    mail_to = _get_emails_for_obj(request)
 
-    assignee = (
-        flag.pull_request.assignee.user if flag.pull_request.assignee else None
-    )
+    assignee = request.assignee.user if request.assignee else None
 
     send_email(
         text,
-        "PR #%s - %s: %s" % (flag.pull_request.id, flag.username, flag.status),
+        "PR #%s - %s: %s" % (request.id, flag.username, flag.status),
         ",".join(mail_to),
         mail_id=flag.mail_id,
-        in_reply_to=flag.pull_request.mail_id,
-        project_name=flag.pull_request.project.fullname,
+        in_reply_to=request.mail_id,
+        project_name=request.project.fullname,
         user_from=flag.username,
-        reporter=flag.pull_request.user.user,
+        reporter=request.user.user,
         assignee=assignee,
     )
 
 
 def notify_new_email(email, user):
-    """ Ask the user to confirm to the email belong to them.
-    """
+    """Ask the user to confirm to the email belong to them."""
 
     root_url = pagure_config.get("APP_URL", flask.request.url_root)
 
@@ -1087,7 +1100,7 @@ Your pagure admin.
 
 
 def notify_new_commits(abspath, project, branch, commits):
-    """ Notify the people following a project's commits that new commits have
+    """Notify the people following a project's commits that new commits have
     been added.
     """
     # string note: abspath, project and branch can only contain ASCII
@@ -1144,7 +1157,7 @@ To view more about the commits, visit:
 
 
 def notify_commit_flag(flag, user):
-    """ Notify the people following a project that a new flag was added
+    """Notify the people following a project that a new flag was added
     to one of its commit.
     """
     text = """
diff --git a/pagure/lib/plugins.py b/pagure/lib/plugins.py
index 9f66848..62ab94d 100644
--- a/pagure/lib/plugins.py
+++ b/pagure/lib/plugins.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 from straight.plugin import load
 
@@ -16,7 +16,7 @@ from pagure.lib.model_base import BASE
 
 
 def get_plugin_names(blacklist=None, without_backref=False):
-    """ Return the list of plugins names.
+    """Return the list of plugins names.
 
     :arg blacklist: name or list of names to not return
     :type blacklist: string or list of strings
@@ -45,13 +45,13 @@ def get_plugin_names(blacklist=None, without_backref=False):
 
 
 def get_plugin_tables():
-    """ Return the list of all plugins. """
+    """Return the list of all plugins."""
     plugins = load("pagure.hooks", subclasses=BASE)
     return plugins
 
 
 def get_plugin(plugin_name):
-    """ Return the list of plugins names. """
+    """Return the list of plugins names."""
     from pagure.hooks import BaseHook
 
     plugins = load("pagure.hooks", subclasses=BaseHook)
@@ -61,7 +61,7 @@ def get_plugin(plugin_name):
 
 
 def get_enabled_plugins(project):
-    """ Returns a list of plugins enabled for a specific project.
+    """Returns a list of plugins enabled for a specific project.
 
     Args:
         project (model.Project): The project to look for.
diff --git a/pagure/lib/query.py b/pagure/lib/query.py
index 8ee4df6..97ff6e7 100644
--- a/pagure/lib/query.py
+++ b/pagure/lib/query.py
@@ -9,7 +9,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 # pylint: disable=too-many-branches
 # pylint: disable=too-many-arguments
@@ -34,7 +34,6 @@ import shutil
 import subprocess
 import tempfile
 import uuid
-
 from collections import Counter
 from math import ceil
 
@@ -45,11 +44,10 @@ import six
 import sqlalchemy
 import sqlalchemy.schema
 import werkzeug.utils
-
-from six.moves.urllib_parse import urlparse, urlencode, parse_qsl
-from sqlalchemy import asc, desc, func, cast, Text
-from sqlalchemy.orm import aliased
 from flask import url_for
+from six.moves.urllib_parse import parse_qsl, urlencode, urlparse
+from sqlalchemy import Text, asc, cast, desc, func
+from sqlalchemy.orm import aliased
 
 import pagure.exceptions
 import pagure.lib.git
@@ -68,7 +66,6 @@ from pagure.lib import model
 # For backward compatibility since this function used to be in this file
 from pagure.lib.model_base import create_session  # noqa
 
-
 REDIS = None
 PAGURE_CI = None
 _log = logging.getLogger(__name__)
@@ -101,7 +98,7 @@ def get_repotypes():
 
 
 class Unspecified(object):
-    """ Custom None object used to indicate that the caller has not made
+    """Custom None object used to indicate that the caller has not made
     a choice for a particular argument.
     """
 
@@ -109,21 +106,20 @@ class Unspecified(object):
 
 
 def set_redis(host, port, dbname):
-    """ Set the redis connection with the specified information. """
+    """Set the redis connection with the specified information."""
     global REDIS
     pool = redis.ConnectionPool(host=host, port=port, db=dbname)
     REDIS = redis.StrictRedis(connection_pool=pool)
 
 
 def set_pagure_ci(services):
-    """ Set the list of CI services supported by this pagure instance. """
+    """Set the list of CI services supported by this pagure instance."""
     global PAGURE_CI
     PAGURE_CI = services
 
 
 def get_user(session, key):
-    """ Searches for a user in the database for a given username or email.
-    """
+    """Searches for a user in the database for a given username or email."""
     user_obj = search_user(session, username=key)
     if not user_obj:
         user_obj = search_user(session, email=key)
@@ -135,16 +131,14 @@ def get_user(session, key):
 
 
 def get_user_by_id(session, userid):
-    """ Searches for a user in the database for a given username or email.
-    """
+    """Searches for a user in the database for a given username or email."""
     query = session.query(model.User).filter(model.User.id == userid)
 
     return query.first()
 
 
 def get_blocked_users(session, username=None, date=None):
-    """ Returns all the users that are blocked in this pagure instance.
-    """
+    """Returns all the users that are blocked in this pagure instance."""
     now = datetime.datetime.utcnow()
     query = session.query(model.User).filter(
         model.User.refuse_sessions_before >= (date or now)
@@ -160,7 +154,7 @@ def get_blocked_users(session, username=None, date=None):
 
 
 def get_next_id(session, projectid):
-    """ Returns the next identifier of a project ticket or pull-request
+    """Returns the next identifier of a project ticket or pull-request
     based on the identifier already in the database.
     """
     query1 = session.query(func.max(model.Issue.id)).filter(
@@ -180,7 +174,7 @@ def get_next_id(session, projectid):
 
 
 def search_user(session, username=None, email=None, token=None, pattern=None):
-    """ Searches the database for the user or users matching the given
+    """Searches the database for the user or users matching the given
     criterias.
 
     :arg session: the session to use to connect to the database.
@@ -223,7 +217,7 @@ def search_user(session, username=None, email=None, token=None, pattern=None):
 
 
 def is_valid_ssh_key(key, fp_hash="SHA256"):
-    """ Validates the ssh key using ssh-keygen. """
+    """Validates the ssh key using ssh-keygen."""
     key = key.strip()
     if not key:
         return None
@@ -247,14 +241,14 @@ def is_valid_ssh_key(key, fp_hash="SHA256"):
 
 
 def are_valid_ssh_keys(keys):
-    """ Checks if all the ssh keys are valid or not. """
+    """Checks if all the ssh keys are valid or not."""
     return all(
         [is_valid_ssh_key(key) is not False for key in keys.split("\n")]
     )
 
 
 def find_ssh_key(session, search_key, username):
-    """ Finds and returns SSHKey matching the requested search_key.
+    """Finds and returns SSHKey matching the requested search_key.
 
     Args:
         session: database session
@@ -281,7 +275,7 @@ def find_ssh_key(session, search_key, username):
 
 
 def create_deploykeys_ssh_keys_on_disk(project, gitolite_keydir):
-    """ Create the ssh keys for the projects' deploy keys on the key dir.
+    """Create the ssh keys for the projects' deploy keys on the key dir.
 
     This method does NOT support multiple ssh keys per deploy key.
     """
@@ -326,7 +320,7 @@ def create_deploykeys_ssh_keys_on_disk(project, gitolite_keydir):
 
 
 def create_user_ssh_keys_on_disk(user, gitolite_keydir):
-    """ Create the ssh keys for the user on the specific folder.
+    """Create the ssh keys for the user on the specific folder.
 
     This is the method allowing to have multiple ssh keys per user.
     """
@@ -376,7 +370,7 @@ def add_issue_comment(
     date_created=None,
     notification=False,
 ):
-    """ Add a comment to an issue. """
+    """Add a comment to an issue."""
     user_obj = get_user(session, user)
 
     issue_comment = model.IssueComment(
@@ -448,7 +442,7 @@ def add_issue_comment(
 
 
 def add_tag_obj(session, obj, tags, user):
-    """ Add a tag to an object (either an issue or a project). """
+    """Add a tag to an object (either an issue or a project)."""
     user_obj = get_user(session, user)
 
     if isinstance(tags, six.string_types):
@@ -498,7 +492,8 @@ def add_tag_obj(session, obj, tags, user):
 
         session.add(dbobjtag)
         # Make sure we won't have SQLAlchemy error before we continue
-        session.flush()
+        # Commit so the tags show up in the notification sent
+        session.commit()
         added_tags.append(tagobj.tag)
 
     if isinstance(obj, model.Issue):
@@ -511,7 +506,7 @@ def add_tag_obj(session, obj, tags, user):
                 msg=dict(
                     issue=obj.to_json(public=True),
                     project=obj.project.to_json(public=True),
-                    tags=added_tags,
+                    tags=sorted(added_tags),
                     agent=user_obj.username,
                 ),
             )
@@ -536,8 +531,9 @@ def add_tag_obj(session, obj, tags, user):
                 topic="pull-request.tag.added",
                 msg=dict(
                     pull_request=obj.to_json(public=True),
+                    pullrequest=obj.to_json(public=True),
                     project=obj.project.to_json(public=True),
-                    tags=added_tags,
+                    tags=sorted(added_tags),
                     agent=user_obj.username,
                 ),
             )
@@ -564,7 +560,7 @@ def add_tag_obj(session, obj, tags, user):
 
 
 def add_issue_assignee(session, issue, assignee, user, notify=True):
-    """ Add an assignee to an issue, in other words, assigned an issue. """
+    """Add an assignee to an issue, in other words, assigned an issue."""
     user_obj = get_user(session, user)
 
     old_assignee = issue.assignee
@@ -641,7 +637,7 @@ def add_issue_assignee(session, issue, assignee, user, notify=True):
 
 
 def add_pull_request_assignee(session, request, assignee, user):
-    """ Add an assignee to a request, in other words, assigned an issue. """
+    """Add an assignee to a request, in other words, assigned an issue."""
     get_user(session, assignee)
     user_obj = get_user(session, user)
 
@@ -654,11 +650,23 @@ def add_pull_request_assignee(session, request, assignee, user):
 
         pagure.lib.notify.notify_assigned_request(request, None, user_obj)
 
+        # Deprecated -- this is not consistent in both the topic and the body:
+        # request vs pull-request (topic) and request vs pullrequest (body)
         pagure.lib.notify.log(
             request.project,
             topic="request.assigned.reset",
             msg=dict(
                 request=request.to_json(public=True),
+                pullrequest=request.to_json(public=True),
+                project=request.project.to_json(public=True),
+                agent=user_obj.username,
+            ),
+        )
+        pagure.lib.notify.log(
+            request.project,
+            topic="pull-request.assigned.reset",
+            msg=dict(
+                pullrequest=request.to_json(public=True),
                 project=request.project.to_json(public=True),
                 agent=user_obj.username,
             ),
@@ -682,11 +690,23 @@ def add_pull_request_assignee(session, request, assignee, user):
             request, assignee_obj, user_obj
         )
 
+        # Deprecated -- this is not consistent in both the topic and the body:
+        # request vs pull-request (topic) and request vs pullrequest (body)
         pagure.lib.notify.log(
             request.project,
             topic="request.assigned.added",
             msg=dict(
                 request=request.to_json(public=True),
+                pullrequest=request.to_json(public=True),
+                project=request.project.to_json(public=True),
+                agent=user_obj.username,
+            ),
+        )
+        pagure.lib.notify.log(
+            request.project,
+            topic="pull-request.assigned.added",
+            msg=dict(
+                pullrequest=request.to_json(public=True),
                 project=request.project.to_json(public=True),
                 agent=user_obj.username,
             ),
@@ -696,7 +716,7 @@ def add_pull_request_assignee(session, request, assignee, user):
 
 
 def add_issue_dependency(session, issue, issue_blocked, user):
-    """ Add a dependency between two issues. """
+    """Add a dependency between two issues."""
     user_obj = get_user(session, user)
 
     if issue.uid == issue_blocked.uid:
@@ -710,7 +730,8 @@ def add_issue_dependency(session, issue, issue_blocked, user):
         )
         session.add(i2i)
         # Make sure we won't have SQLAlchemy error before we continue
-        session.flush()
+        # and commit so the blocking issue appears in the JSON representation
+        session.commit()
         pagure.lib.git.update_git(issue, repo=issue.project)
         pagure.lib.git.update_git(issue_blocked, repo=issue_blocked.project)
 
@@ -753,7 +774,7 @@ def add_issue_dependency(session, issue, issue_blocked, user):
 
 
 def remove_issue_dependency(session, issue, issue_blocked, user):
-    """ Remove a dependency between two issues. """
+    """Remove a dependency between two issues."""
     user_obj = get_user(session, user)
 
     if issue.uid == issue_blocked.uid:
@@ -769,7 +790,8 @@ def remove_issue_dependency(session, issue, issue_blocked, user):
                 issue.parents.remove(parent)
 
         # Make sure we won't have SQLAlchemy error before we continue
-        session.flush()
+        # and commit so the blocking issue appears in the JSON representation
+        session.commit()
         pagure.lib.git.update_git(issue, repo=issue.project)
         pagure.lib.git.update_git(issue_blocked, repo=issue_blocked.project)
 
@@ -814,7 +836,7 @@ def remove_issue_dependency(session, issue, issue_blocked, user):
 
 
 def remove_tags(session, project, tags, user):
-    """ Removes the specified tag of a project. """
+    """Removes the specified tag of a project."""
     user_obj = get_user(session, user)
 
     if not isinstance(tags, list):
@@ -851,7 +873,7 @@ def remove_tags(session, project, tags, user):
         topic="project.tag.removed",
         msg=dict(
             project=project.to_json(public=True),
-            tags=removed_tags,
+            tags=sorted(removed_tags),
             agent=user_obj.username,
         ),
     )
@@ -860,7 +882,7 @@ def remove_tags(session, project, tags, user):
 
 
 def remove_tags_obj(session, obj, tags, user):
-    """ Removes the specified tag(s) of a given object. """
+    """Removes the specified tag(s) of a given object."""
     user_obj = get_user(session, user)
 
     if isinstance(tags, six.string_types):
@@ -886,6 +908,9 @@ def remove_tags_obj(session, obj, tags, user):
                 removed_tags.append(tag)
                 session.delete(objtag)
 
+    # Commit so the tags are updated in the notification sent
+    session.commit()
+
     if isinstance(obj, model.Issue):
         pagure.lib.git.update_git(obj, repo=obj.project)
 
@@ -895,7 +920,7 @@ def remove_tags_obj(session, obj, tags, user):
             msg=dict(
                 issue=obj.to_json(public=True),
                 project=obj.project.to_json(public=True),
-                tags=removed_tags,
+                tags=sorted(removed_tags),
                 agent=user_obj.username,
             ),
         )
@@ -914,8 +939,9 @@ def remove_tags_obj(session, obj, tags, user):
             topic="pull-request.tag.removed",
             msg=dict(
                 pull_request=obj.to_json(public=True),
+                pullrequest=obj.to_json(public=True),
                 project=obj.project.to_json(public=True),
-                tags=removed_tags,
+                tags=sorted(removed_tags),
                 agent=user_obj.username,
             ),
         )
@@ -942,7 +968,7 @@ def edit_issue_tags(
     new_tag_color,
     user,
 ):
-    """ Edits the specified tag of a project. """
+    """Edits the specified tag of a project."""
     user_obj = get_user(session, user)
     old_tag_name = old_tag
 
@@ -1039,7 +1065,7 @@ def edit_issue_tags(
 def add_sshkey_to_project_or_user(
     session, ssh_key, pushaccess, creator, project=None, user=None
 ):
-    """ Add a deploy key to a specified project. """
+    """Add a deploy key to a specified project."""
     if project is None and user is None:
         raise ValueError(
             "SSH Keys need to be added to either a project or a user"
@@ -1106,8 +1132,7 @@ def add_user_to_project(
     branches=None,
     required_groups=None,
 ):
-    """ Add a specified user to a specified project with a specified access
-    """
+    """Add a specified user to a specified project with a specified access"""
 
     new_user_obj = get_user(session, new_user)
 
@@ -1151,7 +1176,7 @@ def add_user_to_project(
         update_read_only_mode(session, project, read_only=True)
         session.add(access_obj)
         session.add(project)
-        session.flush()
+        session.commit()
 
         pagure.lib.notify.log(
             project,
@@ -1178,8 +1203,8 @@ def add_user_to_project(
     # Mark the project as read only, celery will then unmark it
     update_read_only_mode(session, project, read_only=True)
     session.add(project)
-    # Make sure we won't have SQLAlchemy error before we continue
-    session.flush()
+    # Commit so the JSON sent in the notification is up to date
+    session.commit()
 
     pagure.lib.notify.log(
         project,
@@ -1206,7 +1231,7 @@ def add_group_to_project(
     create=False,
     is_admin=False,
 ):
-    """ Add a specified group to a specified project with some access """
+    """Add a specified group to a specified project with some access"""
 
     user_obj = search_user(session, username=user)
     if not user_obj:
@@ -1263,7 +1288,8 @@ def add_group_to_project(
         project.date_modified = datetime.datetime.utcnow()
         update_read_only_mode(session, project, read_only=True)
         session.add(project)
-        session.flush()
+        # Commit so the JSON sent in the notification is up to date
+        session.commit()
 
         pagure.lib.notify.log(
             project,
@@ -1291,7 +1317,8 @@ def add_group_to_project(
     # Mark the project read_only, celery will then unmark it
     update_read_only_mode(session, project, read_only=True)
     session.add(project)
-    session.flush()
+    # Commit so the JSON sent in the notification is up to date
+    session.commit()
 
     pagure.lib.notify.log(
         project,
@@ -1321,7 +1348,7 @@ def add_pull_request_comment(
     notification=False,
     trigger_ci=None,
 ):
-    """ Add a comment to a pull-request. """
+    """Add a comment to a pull-request."""
     user_obj = get_user(session, user)
 
     pr_comment = model.PullRequestComment(
@@ -1425,7 +1452,7 @@ def add_pull_request_comment(
 
 
 def edit_comment(session, parent, comment, user, updated_comment):
-    """ Edit a comment. """
+    """Edit a comment."""
     user_obj = get_user(session, user)
     comment.comment = updated_comment
     comment.edited_on = datetime.datetime.utcnow()
@@ -1499,38 +1526,31 @@ def edit_comment(session, parent, comment, user, updated_comment):
 def add_pull_request_flag(
     session, request, username, percent, comment, url, status, uid, user, token
 ):
-    """ Add a flag to a pull-request. """
+    """Add a flag to a pull-request."""
     user_obj = get_user(session, user)
 
-    action = "added"
-    pr_flag = None
-    if uid:
-        pr_flag = get_pull_request_flag_by_uid(session, request, uid)
-    if pr_flag:
-        action = "updated"
-        pr_flag.comment = comment
-        pr_flag.status = status
-        pr_flag.percent = percent
-        pr_flag.url = url
-    else:
-        pr_flag = model.PullRequestFlag(
-            pull_request_uid=request.uid,
-            uid=uid or uuid.uuid4().hex,
-            username=username,
-            percent=percent,
-            comment=comment,
-            status=status,
-            url=url,
-            user_id=user_obj.id,
-            token_id=token,
-        )
-    request.updated_on = datetime.datetime.utcnow()
-    session.add(pr_flag)
-    # Make sure we won't have SQLAlchemy error before we continue
-    session.flush()
+    action, flag_uid = add_commit_flag(
+        session=session,
+        repo=request.project,
+        commit_hash=request.commit_stop,
+        username=username,
+        status=status,
+        percent=percent,
+        comment=comment,
+        url=url,
+        uid=uid,
+        user=user,
+        token=token,
+    )
+
+    action = action.replace("Flag ", "")
+
+    pr_flag = pagure.lib.query.get_commit_flag_by_uid(
+        session, request.commit_stop, flag_uid
+    )
 
     if request.project.settings.get("notify_on_pull-request_flag"):
-        pagure.lib.notify.notify_pull_request_flag(pr_flag, username)
+        pagure.lib.notify.notify_pull_request_flag(pr_flag, request, username)
 
     pagure.lib.git.update_git(request, repo=request.project)
 
@@ -1560,7 +1580,7 @@ def add_commit_flag(
     user,
     token,
 ):
-    """ Add a flag to a add_commit_flag. """
+    """Add a flag to a add_commit_flag."""
     user_obj = get_user(session, user)
 
     action = "added"
@@ -1605,7 +1625,7 @@ def add_commit_flag(
 
 
 def get_commit_flag(session, project, commit_hash):
-    """ Return the commit flags corresponding to the specified git hash
+    """Return the commit flags corresponding to the specified git hash
     (commitid) in the specified repository.
 
     :arg session: the session with which to connect to the database
@@ -1619,7 +1639,7 @@ def get_commit_flag(session, project, commit_hash):
         session.query(model.CommitFlag)
         .filter(model.CommitFlag.project_id == project.id)
         .filter(model.CommitFlag.commit_hash == commit_hash)
-    )
+    ).order_by(model.CommitFlag.date_updated)
 
     return query.all()
 
@@ -1645,7 +1665,7 @@ def new_project(
     private=False,
     default_branch=None,
 ):
-    """ Create a new project based on the information provided.
+    """Create a new project based on the information provided.
 
     Is an async operation, and returns task ID.
     """
@@ -1745,7 +1765,7 @@ def new_project(
         name,
         add_readme,
         ignore_existing_repo,
-        default_branch,
+        default_branch or pagure_config.get("GIT_DEFAULT_BRANCH"),
     )
 
 
@@ -1768,7 +1788,7 @@ def new_issue(
     assignee=None,
     tags=None,
 ):
-    """ Create a new issue for the specified repo. """
+    """Create a new issue for the specified repo."""
     user_obj = get_user(session, user)
 
     # Only store the priority if there is one in the project
@@ -1853,7 +1873,7 @@ def new_issue(
 
 
 def drop_issue(session, issue, user):
-    """ Delete a specified issue. """
+    """Delete a specified issue."""
     user_obj = get_user(session, user)
 
     repotype = issue.repotype
@@ -1901,7 +1921,7 @@ def new_pull_request(
     commit_start=None,
     commit_stop=None,
 ):
-    """ Create a new pull request on the specified repo. """
+    """Create a new pull request on the specified repo."""
     if not repo_from and not remote_git:
         raise pagure.exceptions.PagureException(
             "Invalid input, you must specify either a local repo or a "
@@ -1982,7 +2002,7 @@ def new_pull_request(
 
 
 def link_pr_to_issue_on_description(session, request):
-    """ Link the given request to issues it may be referring to in its
+    """Link the given request to issues it may be referring to in its
     description if there is a description and such link in it.
     """
     _log.debug("Drop the existing relations")
@@ -2035,7 +2055,7 @@ def link_pr_to_issue_on_description(session, request):
 
 
 def new_tag(session, tag_name, tag_description, tag_color, project_id):
-    """ Return a new tag object """
+    """Return a new tag object"""
     tagobj = model.TagColored(
         tag=tag_name,
         tag_description=tag_description,
@@ -2061,7 +2081,7 @@ def edit_issue(
     milestone=Unspecified,
     private=None,
 ):
-    """ Edit the specified issue.
+    """Edit the specified issue.
 
     :arg session: the session to use to connect to the database.
     :arg issue: the pagure.lib.model.Issue object to edit.
@@ -2176,7 +2196,7 @@ def edit_issue(
             msg=dict(
                 issue=issue.to_json(public=True),
                 project=issue.project.to_json(public=True),
-                fields=list(set(edit)),
+                fields=sorted(set(edit)),
                 agent=user_obj.username,
             ),
         )
@@ -2197,6 +2217,7 @@ def edit_issue(
                             public=True, with_comments=False
                         ),
                         "priorities": issue.project.priorities,
+                        "content_updated": text2markdown(issue.content),
                     }
                 ),
             )
@@ -2208,7 +2229,7 @@ def edit_issue(
 
 
 def update_project_settings(session, repo, settings, user, from_api=False):
-    """ Update the settings of a project.
+    """Update the settings of a project.
 
     If from_api is true, all values that are not specified will be changed
     back to their default value.
@@ -2266,7 +2287,7 @@ def update_project_settings(session, repo, settings, user, from_api=False):
             topic="project.edit",
             msg=dict(
                 project=repo.to_json(public=True),
-                fields=update,
+                fields=sorted(update),
                 agent=user_obj.username,
             ),
         )
@@ -2281,7 +2302,7 @@ def update_project_settings(session, repo, settings, user, from_api=False):
 
 
 def update_user_settings(session, settings, user):
-    """ Update the settings of a project. """
+    """Update the settings of a project."""
     user_obj = get_user(session, user)
 
     update = []
@@ -2307,7 +2328,7 @@ def update_user_settings(session, settings, user):
 
 
 def fork_project(session, user, repo, editbranch=None, editfile=None):
-    """ Fork a given project into the user's forks. """
+    """Fork a given project into the user's forks."""
     if _get_project(session, repo.name, user, repo.namespace) is not None:
         raise pagure.exceptions.RepoExistsException(
             'Repo "forks/%s/%s" already exists' % (user, repo.name)
@@ -2372,8 +2393,7 @@ def search_projects(
     private=None,
     owner=None,
 ):
-    """List existing projects
-    """
+    """List existing projects"""
     projects = session.query(sqlalchemy.distinct(model.Project.id))
 
     if owner is not None and username is not None:
@@ -2619,8 +2639,7 @@ def list_users_projects(
     private=None,
     acls=None,
 ):
-    """List a users projects
-    """
+    """List a users projects"""
     projects = session.query(sqlalchemy.distinct(model.Project.id))
 
     if acls is None:
@@ -2810,8 +2829,7 @@ def list_users_projects(
 
 
 def _get_project(session, name, user=None, namespace=None):
-    """Get a project from the database
-    """
+    """Get a project from the database"""
     case = pagure_config.get("CASE_SENSITIVE", False)
 
     query = session.query(model.Project)
@@ -2877,7 +2895,7 @@ def search_issues(
     order="desc",
     order_key=None,
 ):
-    """ Retrieve one or more issues associated to a project with the given
+    """Retrieve one or more issues associated to a project with the given
     criterias.
 
     Watch out that the closed argument is incompatible with the status
@@ -3214,8 +3232,7 @@ def search_issues(
 
 
 def get_tags_of_project(session, project, pattern=None):
-    """ Returns the list of tags associated with the issues of a project.
-    """
+    """Returns the list of tags associated with the issues of a project."""
     query = (
         session.query(model.TagColored)
         .filter(model.TagColored.tag != "")
@@ -3232,16 +3249,14 @@ def get_tags_of_project(session, project, pattern=None):
 
 
 def get_tag(session, tag):
-    """ Returns a Tag object for the given tag text.
-    """
+    """Returns a Tag object for the given tag text."""
     query = session.query(model.Tag).filter(model.Tag.tag == tag)
 
     return query.first()
 
 
 def get_colored_tag(session, tag, project_id):
-    """ Returns a TagColored object for the given tag text.
-    """
+    """Returns a TagColored object for the given tag text."""
     query = (
         session.query(model.TagColored)
         .filter(model.TagColored.tag == tag)
@@ -3269,8 +3284,7 @@ def search_pull_requests(
     order_key=None,
     search_pattern=None,
 ):
-    """ Retrieve the specified pull-requests.
-    """
+    """Retrieve the specified pull-requests."""
 
     query = session.query(model.PullRequest)
 
@@ -3416,8 +3430,7 @@ def search_pull_requests(
 
 
 def reopen_pull_request(session, request, user):
-    """ Re-Open the provided pull request
-    """
+    """Re-Open the provided pull request"""
     if request.status != "Closed":
         raise pagure.exceptions.PagureException(
             "Trying to reopen a pull request that is not closed"
@@ -3451,8 +3464,7 @@ def reopen_pull_request(session, request, user):
 
 
 def close_pull_request(session, request, user, merged=True):
-    """ Close the provided pull-request.
-    """
+    """Close the provided pull-request."""
     user_obj = get_user(session, user)
 
     if merged is True:
@@ -3499,8 +3511,7 @@ def close_pull_request(session, request, user, merged=True):
 
 
 def reset_status_pull_request(session, project, but_uids=None):
-    """ Reset the status of all opened Pull-Requests of a project.
-    """
+    """Reset the status of all opened Pull-Requests of a project."""
     query = (
         session.query(model.PullRequest)
         .filter(model.PullRequest.project_id == project.id)
@@ -3518,7 +3529,7 @@ def reset_status_pull_request(session, project, but_uids=None):
 
 
 def add_attachment(repo, issue, attachmentfolder, user, filename, filestream):
-    """ Add a file to the attachments folder of repo and update git. """
+    """Add a file to the attachments folder of repo and update git."""
     _log.info(
         "Adding file: %s to the git repo: %s",
         repo.path,
@@ -3557,8 +3568,7 @@ def add_attachment(repo, issue, attachmentfolder, user, filename, filestream):
 
 
 def get_issue_statuses(session):
-    """ Return the complete list of status an issue can have.
-    """
+    """Return the complete list of status an issue can have."""
     output = []
     statuses = session.query(model.StatusIssue).all()
     for status in statuses:
@@ -3567,8 +3577,7 @@ def get_issue_statuses(session):
 
 
 def get_issue_comment(session, issue_uid, comment_id):
-    """ Return a specific comment of a specified issue.
-    """
+    """Return a specific comment of a specified issue."""
     query = (
         session.query(model.IssueComment)
         .filter(model.IssueComment.issue_uid == issue_uid)
@@ -3581,8 +3590,7 @@ def get_issue_comment(session, issue_uid, comment_id):
 def get_issue_comment_by_user_and_comment(
     session, issue_uid, user_id, content
 ):
-    """ Return a specific comment of a specified issue.
-    """
+    """Return a specific comment of a specified issue."""
     query = (
         session.query(model.IssueComment)
         .filter(model.IssueComment.issue_uid == issue_uid)
@@ -3594,8 +3602,7 @@ def get_issue_comment_by_user_and_comment(
 
 
 def get_request_comment(session, request_uid, comment_id):
-    """ Return a specific comment of a specified request.
-    """
+    """Return a specific comment of a specified request."""
     query = (
         session.query(model.PullRequestComment)
         .filter(model.PullRequestComment.pull_request_uid == request_uid)
@@ -3606,7 +3613,7 @@ def get_request_comment(session, request_uid, comment_id):
 
 
 def get_issue_by_uid(session, issue_uid):
-    """ Return the issue corresponding to the specified unique identifier.
+    """Return the issue corresponding to the specified unique identifier.
 
     :arg session: the session to use to connect to the database.
     :arg issue_uid: the unique identifier of an issue. This identifier is
@@ -3623,7 +3630,7 @@ def get_issue_by_uid(session, issue_uid):
 
 
 def get_request_by_uid(session, request_uid):
-    """ Return the request corresponding to the specified unique identifier.
+    """Return the request corresponding to the specified unique identifier.
 
     :arg session: the session to use to connect to the database.
     :arg request_uid: the unique identifier of a request. This identifier is
@@ -3642,7 +3649,7 @@ def get_request_by_uid(session, request_uid):
 
 
 def get_pull_request_flag_by_uid(session, request, flag_uid):
-    """ Return the flag corresponding to the specified unique identifier.
+    """Return the flag corresponding to the specified unique identifier.
 
     :arg session: the session to use to connect to the database.
     :arg request: the pull-request that was flagged
@@ -3664,7 +3671,7 @@ def get_pull_request_flag_by_uid(session, request, flag_uid):
 
 
 def get_commit_flag_by_uid(session, commit_hash, flag_uid):
-    """ Return the flag corresponding to the specified unique identifier.
+    """Return the flag corresponding to the specified unique identifier.
 
     :arg session: the session to use to connect to the database.
     :arg commit_hash: the hash of the commit that got flagged
@@ -3694,7 +3701,7 @@ def set_up_user(
     ssh_key=None,
     keydir=None,
 ):
-    """ Set up a new user into the database or update its information. """
+    """Set up a new user into the database or update its information."""
     user = search_user(session, username=username)
     if not user:
         user = model.User(
@@ -3726,8 +3733,8 @@ def set_up_user(
 
 
 def allowed_emailaddress(email):
-    """ check if email domains are restricted and if a given email address
-    is allowed. """
+    """check if email domains are restricted and if a given email address
+    is allowed."""
     allowed_email_domains = pagure_config.get("ALLOWED_EMAIL_DOMAINS", None)
     if allowed_email_domains:
         for domain in allowed_email_domains:
@@ -3743,7 +3750,7 @@ def allowed_emailaddress(email):
 
 
 def add_email_to_user(session, user, user_email):
-    """ Add the provided email to the specified user. """
+    """Add the provided email to the specified user."""
     try:
         allowed_emailaddress(user_email)
     except pagure.exceptions.PagureException:
@@ -3758,7 +3765,7 @@ def add_email_to_user(session, user, user_email):
 
 
 def update_user_ssh(session, user, ssh_key, keydir, update_only=False):
-    """ Set up a new user into the database or update its information. """
+    """Set up a new user into the database or update its information."""
     if isinstance(user, six.string_types):
         user = get_user(session, user)
 
@@ -3808,7 +3815,7 @@ def avatar_url_from_email(email, size=64, default="retro", dns=False):
 
 
 def update_tags(session, obj, tags, username):
-    """ Update the tags of a specified object (adding or removing them).
+    """Update the tags of a specified object (adding or removing them).
     This object can be either an issue or a project.
 
     """
@@ -3838,9 +3845,7 @@ def update_tags(session, obj, tags, username):
 
 
 def update_dependency_issue(session, repo, issue, depends, username):
-    """ Update the dependency of a specified issue (adding or removing them)
-
-    """
+    """Update the dependency of a specified issue (adding or removing them)"""
     if isinstance(depends, six.string_types):
         depends = [depends]
 
@@ -3883,7 +3888,7 @@ def update_dependency_issue(session, repo, issue, depends, username):
 
 
 def update_blocked_issue(session, repo, issue, blocks, username):
-    """ Update the upstream dependency of a specified issue (adding or
+    """Update the upstream dependency of a specified issue (adding or
     removing them)
 
     """
@@ -3931,8 +3936,7 @@ def update_blocked_issue(session, repo, issue, blocks, username):
 
 
 def add_user_pending_email(session, userobj, email):
-    """ Add the provided email to the specified user.
-    """
+    """Add the provided email to the specified user."""
     try:
         allowed_emailaddress(email)
     except pagure.exceptions.PagureException:
@@ -3961,7 +3965,7 @@ def add_user_pending_email(session, userobj, email):
 
 
 def resend_pending_email(session, userobj, email):
-    """ Resend to the user the confirmation email for the provided email
+    """Resend to the user the confirmation email for the provided email
     address.
     """
     other_user = search_user(session, email=email)
@@ -3984,7 +3988,7 @@ def resend_pending_email(session, userobj, email):
 
 
 def search_pending_email(session, email=None, token=None):
-    """ Searches the database for the pending email matching the given
+    """Searches the database for the pending email matching the given
     criterias.
 
     :arg session: the session to use to connect to the database.
@@ -4010,9 +4014,7 @@ def search_pending_email(session, email=None, token=None):
 
 
 def generate_hook_token(session):
-    """ For each project in the database, re-generate a unique hook_token.
-
-    """
+    """For each project in the database, re-generate a unique hook_token."""
 
     for project in search_projects(session):
         project.hook_token = pagure.lib.login.id_generator(40)
@@ -4021,9 +4023,7 @@ def generate_hook_token(session):
 
 
 def get_group_types(session, group_type=None):
-    """ Return the list of type a group can have.
-
-    """
+    """Return the list of type a group can have."""
     query = session.query(model.PagureGroupType).order_by(
         model.PagureGroupType.group_type
     )
@@ -4044,9 +4044,7 @@ def search_groups(
     limit=None,
     count=False,
 ):
-    """ Return the groups based on the criteria specified.
-
-    """
+    """Return the groups based on the criteria specified."""
     query = session.query(model.PagureGroup).order_by(
         model.PagureGroup.group_type, model.PagureGroup.group_name
     )
@@ -4085,7 +4083,7 @@ def search_groups(
 def add_user_to_group(
     session, username, group, user, is_admin, from_external=False
 ):
-    """ Add the specified user to the given group.
+    """Add the specified user to the given group.
 
     from_external indicates whether this is a remotely synced group.
     """
@@ -4128,8 +4126,7 @@ def add_user_to_group(
 
 
 def edit_group_info(session, group, display_name, description, user, is_admin):
-    """ Edit the information regarding a given group.
-    """
+    """Edit the information regarding a given group."""
     action_user = user
     user = search_user(session, username=user)
     if not user:
@@ -4182,8 +4179,7 @@ def delete_user_of_group(
     force=False,
     from_external=False,
 ):
-    """ Removes the specified user from the given group.
-    """
+    """Removes the specified user from the given group."""
     group_obj = search_groups(session, group_name=groupname)
 
     if not group_obj:
@@ -4239,8 +4235,7 @@ def add_group(
     is_admin,
     blacklist,
 ):
-    """ Creates a new group with the given information.
-    """
+    """Creates a new group with the given information."""
     if " " in group_name:
         raise pagure.exceptions.PagureException(
             "Spaces are not allowed in group names: %s" % group_name
@@ -4298,7 +4293,7 @@ def add_group(
 
 
 def get_user_group(session, userid, groupid):
-    """ Return a specific user_group for the specified group and user
+    """Return a specific user_group for the specified group and user
     identifiers.
 
     :arg session: the session with which to connect to the database.
@@ -4314,7 +4309,7 @@ def get_user_group(session, userid, groupid):
 
 
 def is_group_member(session, user, groupname):
-    """ Return whether the user is a member of the specified group. """
+    """Return whether the user is a member of the specified group."""
     if not user:
         return False
 
@@ -4326,7 +4321,7 @@ def is_group_member(session, user, groupname):
 
 
 def get_api_token(session, token_str):
-    """ Return the Token object corresponding to the provided token string
+    """Return the Token object corresponding to the provided token string
     if there is any, returns None otherwise.
     """
     query = session.query(model.Token).filter(model.Token.id == token_str)
@@ -4335,7 +4330,7 @@ def get_api_token(session, token_str):
 
 
 def get_acls(session, restrict=None):
-    """ Returns all the possible ACLs a token can have according to the
+    """Returns all the possible ACLs a token can have according to the
     database.
     """
     query = session.query(model.ACL).order_by(model.ACL.name)
@@ -4351,7 +4346,7 @@ def get_acls(session, restrict=None):
 def add_token_to_user(
     session, project, acls, username, expiration_date, description=None
 ):
-    """ Create a new token for the specified user on the specified project
+    """Create a new token for the specified user on the specified project
     with the given ACLs.
     """
     acls_obj = session.query(model.ACL).filter(model.ACL.name.in_(acls)).all()
@@ -4385,7 +4380,7 @@ def add_token_to_user(
 
 
 def _convert_markdown(md_processor, text):
-    """ Small function converting the text to html using the given markdown
+    """Small function converting the text to html using the given markdown
     processor.
 
     This was done in order to make testing it easier.
@@ -4394,8 +4389,7 @@ def _convert_markdown(md_processor, text):
 
 
 def text2markdown(text, extended=True, readme=False):
-    """ Simple text to html converter using the markdown library.
-    """
+    """Simple text to html converter using the markdown library."""
     extensions = [
         "markdown.extensions.def_list",
         "markdown.extensions.fenced_code",
@@ -4450,7 +4444,7 @@ def text2markdown(text, extended=True, readme=False):
 
 
 def filter_img_src(name, value):
-    """ Filter in img html tags images coming from a different domain. """
+    """Filter in img html tags images coming from a different domain."""
     if name in ("alt", "height", "width", "class", "data-src"):
         return True
     if name == "src":
@@ -4462,7 +4456,7 @@ def filter_img_src(name, value):
 
 
 def clean_input(text, ignore=None):
-    """ For a given html text, escape everything we do not want to support
+    """For a given html text, escape everything we do not want to support
     to avoid potential security breach.
     """
     if ignore and not isinstance(ignore, (tuple, set, list)):
@@ -4519,6 +4513,7 @@ def clean_input(text, ignore=None):
         "del",
         "cite",
         "noscript",
+        "colgroup",
     ]
     if ignore:
         for tag in ignore:
@@ -4536,8 +4531,7 @@ def clean_input(text, ignore=None):
 
 
 def could_be_text(text):
-    """ Returns whether we think this chain of character could be text or not
-    """
+    """Returns whether we think this chain of character could be text or not"""
     try:
         text.decode("utf-8")
         return True
@@ -4683,7 +4677,7 @@ def get_pull_request_of_user(
 
 
 def update_watch_status(session, project, user, watch):
-    """ Update the user status for watching a project.
+    """Update the user status for watching a project.
 
     The watch status can be:
         -1: reset the watch status to default
@@ -4756,8 +4750,7 @@ def update_watch_status(session, project, user, watch):
 def get_watch_level_on_repo(
     session, user, repo, repouser=None, namespace=None
 ):
-    """ Get a list representing the watch level of the user on the project.
-    """
+    """Get a list representing the watch level of the user on the project."""
     # If a user wasn't passed in, we can't determine their watch level
     if user is None:
         return []
@@ -4834,7 +4827,7 @@ def get_watch_level_on_repo(
 
 
 def user_watch_list(session, user, exclude_groups=None):
-    """ Returns list of all the projects which the user is watching """
+    """Returns list of all the projects which the user is watching"""
 
     user_obj = search_user(session, username=user)
     if not user_obj:
@@ -4875,7 +4868,7 @@ def user_watch_list(session, user, exclude_groups=None):
 
 
 def set_watch_obj(session, user, obj, watch_status):
-    """ Set the watch status of the user on the specified object.
+    """Set the watch status of the user on the specified object.
 
     Objects can be either an issue or a pull-request
     """
@@ -4924,8 +4917,7 @@ def set_watch_obj(session, user, obj, watch_status):
 
 
 def get_watch_list(session, obj):
-    """ Return a list of all the users that are watching the "object"
-    """
+    """Return a list of all the users that are watching the "object" """
     private = False
     if obj.isa == "issue":
         private = obj.private
@@ -4957,6 +4949,10 @@ def get_watch_list(session, obj):
     # Add the user of the project
     users.add(obj.project.user.username)
 
+    # Add the assignee if there is one
+    if obj.assignee:
+        users.add(obj.assignee.username)
+
     # Add the regular contributors
     for contributor in obj.project.users:
         users.add(contributor.username)
@@ -4988,8 +4984,7 @@ def get_watch_list(session, obj):
 
 
 def save_report(session, repo, name, url, username):
-    """ Save the report of issues based on the given URL of the project.
-    """
+    """Save the report of issues based on the given URL of the project."""
     url_obj = urlparse(url)
     url = url_obj.geturl().replace(url_obj.query, "")
     query = {}
@@ -5008,7 +5003,7 @@ def save_report(session, repo, name, url, username):
 
 
 def set_custom_key_fields(session, project, fields, types, data, notify=None):
-    """ Set or update the custom key fields of a project with the values
+    """Set or update the custom key fields of a project with the values
     provided.  "data" is currently only used for lists and dates
     """
 
@@ -5055,8 +5050,7 @@ def set_custom_key_fields(session, project, fields, types, data, notify=None):
 
 
 def set_custom_key_value(session, issue, key, value):
-    """ Set or update the value of the specified custom key.
-    """
+    """Set or update the value of the specified custom key."""
 
     query = (
         session.query(model.IssueValues)
@@ -5120,7 +5114,7 @@ def set_custom_key_value(session, issue, key, value):
 
 
 def get_yearly_stats_user(session, user, date, tz="UTC"):
-    """ Return the activity of the specified user in the year preceding the
+    """Return the activity of the specified user in the year preceding the
     specified date. 'offset' is intended to be a timezone offset from UTC,
     in minutes: you can discover the offset for a timezone and pass that
     in order for the results to be relative to that timezone. Note, offset
@@ -5146,7 +5140,7 @@ def get_yearly_stats_user(session, user, date, tz="UTC"):
 
 
 def get_user_activity_day(session, user, date, tz="UTC"):
-    """ Return the activity of the specified user on the specified date.
+    """Return the activity of the specified user on the specified date.
     'offset' is intended to be a timezone offset from UTC, in minutes:
     you can discover the offset for a timezone and pass that, so this
     will return activity that occurred on the specified date in the
@@ -5201,7 +5195,7 @@ def get_watchlist_messages(session, user, limit=None):
 
 
 def log_action(session, action, obj, user_obj):
-    """ Log an user action on a project/issue/PR. """
+    """Log an user action on a project/issue/PR."""
     project_id = None
     if obj.isa in ["issue", "pull-request"]:
         project_id = obj.project_id
@@ -5235,7 +5229,7 @@ def log_action(session, action, obj, user_obj):
 
 
 def email_logs_count(session, email):
-    """ Returns the number of logs associated with a given email."""
+    """Returns the number of logs associated with a given email."""
     query = session.query(model.PagureLog).filter(
         model.PagureLog.user_email == email
     )
@@ -5244,7 +5238,7 @@ def email_logs_count(session, email):
 
 
 def update_log_email_user(session, email, user):
-    """ Update the logs with the provided email to point to the specified
+    """Update the logs with the provided email to point to the specified
     user.
     """
     session.query(model.PagureLog).filter(
@@ -5253,7 +5247,7 @@ def update_log_email_user(session, email, user):
 
 
 def get_custom_key(session, project, keyname):
-    """ Returns custom key object given it's name and the project """
+    """Returns custom key object given it's name and the project"""
 
     query = (
         session.query(model.IssueKeys)
@@ -5265,8 +5259,7 @@ def get_custom_key(session, project, keyname):
 
 
 def get_active_milestones(session, project):
-    """ Returns the list of all the active milestones for a given project.
-    """
+    """Returns the list of all the active milestones for a given project."""
 
     query = (
         session.query(model.Issue.milestone)
@@ -5279,7 +5272,7 @@ def get_active_milestones(session, project):
 
 
 def add_metadata_update_notif(session, obj, messages, user):
-    """ Add a notification to the specified issue with the given messages
+    """Add a notification to the specified issue with the given messages
     which should reflect changes made to the meta-data of the issue.
     """
     if not messages:
@@ -5380,14 +5373,14 @@ def tokenize_search_string(pattern):
 
 
 def get_access_levels(session):
-    """ Returns all the access levels a user/group can have for a project """
+    """Returns all the access levels a user/group can have for a project"""
 
     access_level_objs = session.query(model.AccessLevels).all()
     return [access_level.access for access_level in access_level_objs]
 
 
 def get_obj_access(session, project_obj, obj):
-    """ Returns the level of access the user/group has on the project.
+    """Returns the level of access the user/group has on the project.
 
     :arg session: the session to use to connect to the database.
     :arg project_obj: SQLAlchemy object of Project class
@@ -5419,7 +5412,7 @@ def search_token(
     expired=False,
     description=None,
 ):
-    """ Searches the API tokens corresponding to the criterias specified.
+    """Searches the API tokens corresponding to the criterias specified.
 
     :arg session: the session to use to connect to the database.
     :arg acls: List of the ACL associated with these API tokens
@@ -5465,7 +5458,7 @@ def search_token(
 
 
 def set_project_owner(session, project, user, required_groups=None):
-    """ Set the ownership of a project
+    """Set the ownership of a project
     :arg session: the session to use to connect to the database.
     :arg project: a Project object representing the project's ownership to
     change.
@@ -5572,7 +5565,7 @@ def get_pagination_metadata(
 
 
 def update_star_project(session, repo, star, user):
-    """ Unset or set the star status depending on the star value.
+    """Unset or set the star status depending on the star value.
 
     :arg session: the session to use to connect to the database.
     :arg repo: a model.Project object representing the project to star/unstar
@@ -5594,7 +5587,7 @@ def update_star_project(session, repo, star, user):
 
 
 def _star_project(session, repo, user):
-    """ Star a project
+    """Star a project
 
     :arg session: Session object to connect to db with
     :arg repo: model.Project object representing the repo to star
@@ -5610,7 +5603,7 @@ def _star_project(session, repo, user):
 
 
 def _unstar_project(session, repo, user):
-    """ Unstar a project
+    """Unstar a project
     :arg session: Session object to connect to db with
     :arg repo: model.Project object representing the repo to unstar
     :arg user: model.User object who is unstarring this repo
@@ -5631,7 +5624,7 @@ def _unstar_project(session, repo, user):
 
 
 def _get_stargazer_obj(session, repo, user):
-    """ Query the db to find stargazer object with given repo and user
+    """Query the db to find stargazer object with given repo and user
     :arg session: Session object to connect to db with
     :arg repo: model.Project object
     :arg user: model.User object
@@ -5650,7 +5643,7 @@ def _get_stargazer_obj(session, repo, user):
 
 
 def has_starred(session, repo, user):
-    """ Check if a given user has starred a particular project
+    """Check if a given user has starred a particular project
 
     :arg session: The session object to query the db with
     :arg repo: model.Project object for which the star is checked
@@ -5668,7 +5661,7 @@ def has_starred(session, repo, user):
 
 
 def update_read_only_mode(session, repo, read_only=True):
-    """ Remove the read only mode from the project
+    """Remove the read only mode from the project
 
     :arg session: The session object to query the db with
     :arg repo: model.Project object to mark/unmark read only
@@ -5691,8 +5684,8 @@ def update_read_only_mode(session, repo, read_only=True):
         session.add(repo)
 
 
-def issues_history_stats(session, project, detailed=False):
-    """ Returns the number of opened issues on the specified project over
+def issues_history_stats(session, project, detailed=False, weeks_range=53):
+    """Returns the number of opened issues on the specified project over
     the last 365 days
 
     :arg session: The session object to query the db with
@@ -5717,7 +5710,7 @@ def issues_history_stats(session, project, detailed=False):
         .order_by(sqlalchemy.asc(model.Issue.closed_at))
         .first()
     )
-    a_year_ago = tomorrow - datetime.timedelta(days=(53 * 7))
+    a_year_ago = tomorrow - datetime.timedelta(days=(weeks_range * 7))
     if oldest_closed and oldest_closed.closed_at < a_year_ago:
         to_ignore = 0
     else:
@@ -5734,7 +5727,7 @@ def issues_history_stats(session, project, detailed=False):
     # For each week from tomorrow, get the number of open tickets
 
     output = {}
-    for week in range(53):
+    for week in range(weeks_range):
         end = tomorrow - datetime.timedelta(days=(week * 7))
         start = end - datetime.timedelta(days=7)
         closed_ticket = (
@@ -5775,7 +5768,7 @@ def issues_history_stats(session, project, detailed=False):
 def get_authorized_project(
     session, project_name, user=None, namespace=None, asuser=None
 ):
-    """ Retrieving the project with user permission constraint
+    """Retrieving the project with user permission constraint
 
     :arg session: The SQLAlchemy session to use
     :type session: sqlalchemy.orm.session.Session
@@ -5801,7 +5794,7 @@ def get_authorized_project(
 
 
 def get_project_family(session, project):
-    """ Retrieve the family of the specified project, ie: all the forks
+    """Retrieve the family of the specified project, ie: all the forks
     of the main project.
     If the specified project is a fork, let's work our way up the chain
     until we find the main project so we can go down and get all the forks
@@ -5836,7 +5829,7 @@ def get_project_family(session, project):
 
 
 def link_pr_issue(session, issue, request, origin="commit"):
-    """ Associate the specified issue with the specified pull-requets.
+    """Associate the specified issue with the specified pull-requets.
 
     :arg session: The SQLAlchemy session to use
     :type session: sqlalchemy.orm.session.Session
@@ -5858,7 +5851,7 @@ def link_pr_issue(session, issue, request, origin="commit"):
 
 
 def remove_user_of_project(session, user, project, agent):
-    """ Remove the specified user from the given project.
+    """Remove the specified user from the given project.
 
     :arg session: the session with which to connect to the database.
     :arg user: an pagure.lib.model.User object to remove from the project.
@@ -5900,7 +5893,7 @@ def remove_user_of_project(session, user, project, agent):
 
 
 def create_board(session, project, name, active, tag):
-    """ Create a board on a given project.
+    """Create a board on a given project.
 
     :arg session: the session with which to connect to the database.
     :arg project: the model.Project of the project that is creating the
@@ -5925,7 +5918,7 @@ def create_board(session, project, name, active, tag):
 
 
 def edit_board(session, project, name, active, tag, bg_color=None):
-    """ Edit an existing board on a given project.
+    """Edit an existing board on a given project.
 
     :arg session: the session with which to connect to the database.
     :arg project: the model.Project of the project that is creating the
@@ -5963,7 +5956,7 @@ def edit_board(session, project, name, active, tag, bg_color=None):
 
 
 def delete_board(session, project, names):
-    """ Delete boards of a given project.
+    """Delete boards of a given project.
 
     :arg session: the session with which to connect to the database.
     :arg project: the model.Project of the project that is creating the
@@ -5985,7 +5978,7 @@ def delete_board(session, project, names):
 def update_board_status(
     session, board, name, rank, default, close, close_status, bg_color
 ):
-    """ Create or update the board statuses of a project.
+    """Create or update the board statuses of a project.
 
     :arg session: the session with which to connect to the database.
     :arg board: the model.Board of the board being updated.
@@ -6031,7 +6024,7 @@ def update_board_status(
 def add_issue_to_boards(
     session, issue, board_name, user, status_id=None, rank=None
 ):
-    """ Add the given issue to the boards specified.
+    """Add the given issue to the boards specified.
 
     :arg session: the session with which to connect to the database.
     :arg issue: the model.Issue of the issue to add to the boards.
@@ -6072,13 +6065,15 @@ def add_issue_to_boards(
         rank = len(status.boards_issues) + 1
 
     board_issue = model.BoardIssues(
-        issue_uid=issue.uid, status_id=status.id, rank=rank,
+        issue_uid=issue.uid,
+        status_id=status.id,
+        rank=rank,
     )
     session.add(board_issue)
 
 
 def remove_issue_from_boards(session, issue, board_names, user):
-    """ Remove the given issue from the specified boards.
+    """Remove the given issue from the specified boards.
 
     :arg session: the session with which to connect to the database.
     :arg issue: the model.Issue of the issue to add to the boards.
@@ -6103,8 +6098,7 @@ def update_ticket_board_status(
     ticket_uid=None,
     ticket_id=None,
 ):
-    """ Set the status of a ticket on a given board.
-    """
+    """Set the status of a ticket on a given board."""
     if not ticket_uid and not ticket_id:
         raise pagure.exceptions.PagureException(
             "One of ticket_id/ticket_uid must be provided"
@@ -6171,7 +6165,9 @@ def update_ticket_board_status(
     if board.name not in seen:
         _log.debug("Adding to a new board")
         board_issue = model.BoardIssues(
-            issue_uid=ticket.uid, status_id=status.id, rank=rank,
+            issue_uid=ticket.uid,
+            status_id=status.id,
+            rank=rank,
         )
         session.add(board_issue)
 
@@ -6201,7 +6197,10 @@ def update_ticket_board_status(
     elif not status.close and ticket.status != "Open":
         comments.extend(
             edit_issue(
-                session=session, issue=ticket, user=user, status="Open",
+                session=session,
+                issue=ticket,
+                user=user,
+                status="Open",
             )
         )
         session.add(ticket)
@@ -6214,3 +6213,26 @@ def update_ticket_board_status(
             user=user,
             notification=True,
         )
+
+
+def find_warning_characters(repo_obj, commits):
+    """Return whether the given list of commits of the specified repository
+    object contains forbidden characters or not.
+    """
+    warn_characters = pagure_config["PR_WARN_CHARACTERS"]
+
+    for commit in commits:
+        if commit.parents:
+            diff = repo_obj.diff(commit.parents[0], commit)
+        else:
+            # First commit in the repo
+            diff = commit.tree.diff_to_tree(swap=True)
+
+        for patch in diff:
+            for hunk in patch.hunks:
+                for line in hunk.lines:
+                    subset = [c for c in line.content if c in warn_characters]
+                    if subset:
+                        return True
+
+    return False
diff --git a/pagure/lib/repo.py b/pagure/lib/repo.py
index fb6774d..524ea28 100644
--- a/pagure/lib/repo.py
+++ b/pagure/lib/repo.py
@@ -7,7 +7,7 @@
    Pierre-Yves Chibon <pingou@pingoured.fr>
 
 """
-from __future__ import print_function, unicode_literals, absolute_import
+from __future__ import absolute_import, print_function, unicode_literals
 
 import logging
 import subprocess
@@ -17,12 +17,11 @@ import pygit2
 import pagure
 import pagure.exceptions
 
-
 _log = logging.getLogger(__name__)
 
 
 def get_pygit2_version():
-    """ Return pygit2 version as a tuple of integers.
+    """Return pygit2 version as a tuple of integers.
     This is needed for correct version comparison.
     """
     return tuple([int(i) for i in pygit2.__version__.split(".")])
@@ -50,14 +49,14 @@ def run_command(command, cwd=None):
 
 
 class PagureRepo(pygit2.Repository):
-    """ An utility class allowing to go around pygit2's inability to be
+    """An utility class allowing to go around pygit2's inability to be
     stable.
 
     """
 
     @staticmethod
     def clone(path_from, path_to, checkout_branch=None, bare=False):
-        """ Clone the git repo at the specified path to the specified location.
+        """Clone the git repo at the specified path to the specified location.
 
         This method is meant to replace pygit2.clone_repository which for us
         leaks file descriptors on large project leading to "Too many open files
@@ -79,7 +78,7 @@ class PagureRepo(pygit2.Repository):
 
     @staticmethod
     def push(remote, refname):
-        """ Push the given reference to the specified remote. """
+        """Push the given reference to the specified remote."""
         pygit2_version = get_pygit2_version()
         if pygit2_version >= (0, 22):
             remote.push([refname])
@@ -87,7 +86,7 @@ class PagureRepo(pygit2.Repository):
             remote.push(refname)
 
     def pull(self, remote_name="origin", branch="master", force=False):
-        """ pull changes for the specified remote (defaults to origin).
+        """pull changes for the specified remote (defaults to origin).
 
         Code from MichaelBoselowitz at:
         https://github.com/MichaelBoselowitz/pygit2-examples/blob/
@@ -133,7 +132,7 @@ class PagureRepo(pygit2.Repository):
 
     @staticmethod
     def log(path, log_options=None, target=None, fromref=None):
-        """ Run git log with the specified options at the specified target.
+        """Run git log with the specified options at the specified target.
 
         This method runs the system's `git log` command since pygit2 doesn't
         offer us the possibility to do this via them.
@@ -159,7 +158,7 @@ class PagureRepo(pygit2.Repository):
 
     @staticmethod
     def get_active_branches(path, nbranch=8, catch_exception=False):
-        """ Returns the active branches in the git repo at the specified
+        """Returns the active branches in the git repo at the specified
         location.
 
         :arg path: the location of the git repo
diff --git a/pagure/lib/tasks.py b/pagure/lib/tasks.py
index b3a6f6d..05a0ed0 100644
--- a/pagure/lib/tasks.py
+++ b/pagure/lib/tasks.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import collections
 import datetime
@@ -22,7 +22,6 @@ import time
 import arrow
 import pygit2
 import six
-
 from celery import Celery
 from celery.result import AsyncResult
 from celery.signals import after_setup_task_logger
@@ -32,12 +31,12 @@ from sqlalchemy.exc import SQLAlchemyError
 import pagure.lib.git
 import pagure.lib.git_auth
 import pagure.lib.link
-import pagure.lib.query
 import pagure.lib.model
+import pagure.lib.query
 import pagure.lib.repo
 import pagure.utils
-from pagure.lib.tasks_utils import pagure_task
 from pagure.config import config as pagure_config
+from pagure.lib.tasks_utils import pagure_task
 from pagure.utils import get_parent_repo_path
 
 # logging.config.dictConfig(pagure_config.get('LOGGING') or {'version': 1})
@@ -49,7 +48,11 @@ if os.environ.get("PAGURE_BROKER_URL"):
 elif pagure_config.get("BROKER_URL"):
     broker_url = pagure_config["BROKER_URL"]
 else:
-    broker_url = "redis://%s" % pagure_config["REDIS_HOST"]
+    broker_url = "redis://%s:%d/%d" % (
+        pagure_config["REDIS_HOST"],
+        pagure_config["REDIS_PORT"],
+        pagure_config["REDIS_DB"],
+    )
 
 conn = Celery("tasks", broker=broker_url, backend=broker_url)
 conn.conf.update(pagure_config["CELERY_CONFIG"])
@@ -61,7 +64,7 @@ def augment_celery_log(**kwargs):
 
 
 def get_result(uuid):
-    """ Returns the AsyncResult object for a given task.
+    """Returns the AsyncResult object for a given task.
 
     :arg uuid: the unique identifier of the task to retrieve.
     :type uuid: str
@@ -82,7 +85,7 @@ def ret(endpoint, **kwargs):
 def generate_gitolite_acls(
     self, session, namespace=None, name=None, user=None, group=None
 ):
-    """ Generate the gitolite configuration file either entirely or for a
+    """Generate the gitolite configuration file either entirely or for a
     specific project.
 
     :arg session: SQLAlchemy session object
@@ -131,7 +134,7 @@ def generate_gitolite_acls(
 @conn.task(queue=pagure_config.get("GITOLITE_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def gitolite_post_compile_only(self, session):
-    """ Do gitolite post-processing only. Most importantly, this processes SSH
+    """Do gitolite post-processing only. Most importantly, this processes SSH
     keys used by gitolite. This is an optimization task that's supposed to be
     used if you only need to run `gitolite trigger POST_COMPILE` without
     touching any other gitolite configuration
@@ -149,7 +152,7 @@ def gitolite_post_compile_only(self, session):
 def delete_project(
     self, session, namespace=None, name=None, user=None, action_user=None
 ):
-    """ Delete a project in pagure.
+    """Delete a project in pagure.
 
     This is achieved in three steps:
     - Remove the project from gitolite.conf
@@ -222,7 +225,7 @@ def create_project(
     ignore_existing_repo,
     default_branch=None,
 ):
-    """ Create a project.
+    """Create a project.
 
     :arg session: SQLAlchemy session object
     :type session: sqlalchemy.orm.session.Session
@@ -332,7 +335,7 @@ def create_project(
 def update_git(
     self, session, name, namespace, user, ticketuid=None, requestuid=None
 ):
-    """ Update the JSON representation of either a ticket or a pull-request
+    """Update the JSON representation of either a ticket or a pull-request
     depending on the argument specified.
     """
     project = pagure.lib.query._get_project(
@@ -364,7 +367,7 @@ def update_git(
 @conn.task(queue=pagure_config.get("SLOW_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def clean_git(self, session, name, namespace, user, obj_repotype, obj_uid):
-    """ Remove the JSON representation of a ticket on the git repository
+    """Remove the JSON representation of a ticket on the git repository
     for tickets.
     """
     project = pagure.lib.query._get_project(
@@ -393,8 +396,7 @@ def update_file_in_git(
     username,
     email,
 ):
-    """ Update a file in the specified git repo.
-    """
+    """Update a file in the specified git repo."""
     userobj = pagure.lib.query.search_user(session, username=username)
     project = pagure.lib.query._get_project(
         session, namespace=namespace, name=name, user=user
@@ -424,8 +426,7 @@ def update_file_in_git(
 @conn.task(queue=pagure_config.get("MEDIUM_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def delete_branch(self, session, name, namespace, user, branchname):
-    """ Delete a branch from a git repo.
-    """
+    """Delete a branch from a git repo."""
     project = pagure.lib.query._get_project(
         session, namespace=namespace, name=name, user=user
     )
@@ -456,7 +457,7 @@ def fork(
     editbranch,
     editfile,
 ):
-    """ Forks the specified project for the specified user.
+    """Forks the specified project for the specified user.
 
     :arg namespace: the namespace of the project
     :type namespace: str
@@ -514,6 +515,11 @@ def fork(
                 with open(http_clone_file, "w"):
                     pass
 
+        # Finally set the default branch to be the same as the parent
+        repo_from_obj = pygit2.Repository(repo_from.repopath("main"))
+        repo_to_obj = pygit2.Repository(repo_to.repopath("main"))
+        repo_to_obj.set_head(repo_from_obj.lookup_reference("HEAD").target)
+
         pagure.lib.notify.log(
             repo_to,
             topic="project.forked",
@@ -549,8 +555,7 @@ def fork(
 @conn.task(queue=pagure_config.get("FAST_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def pull_remote_repo(self, session, remote_git, branch_from):
-    """ Clone a remote git repository locally for remote PRs.
-    """
+    """Clone a remote git repository locally for remote PRs."""
 
     clonepath = pagure.utils.get_remote_repo_path(
         remote_git, branch_from, ignore_non_exist=True
@@ -566,7 +571,7 @@ def pull_remote_repo(self, session, remote_git, branch_from):
 @conn.task(queue=pagure_config.get("MEDIUM_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def refresh_remote_pr(self, session, name, namespace, user, requestid):
-    """ Refresh the local clone of a git repository used in a remote
+    """Refresh the local clone of a git repository used in a remote
     pull-request.
     """
     project = pagure.lib.query._get_project(
@@ -603,8 +608,7 @@ def refresh_remote_pr(self, session, name, namespace, user, requestid):
 @conn.task(queue=pagure_config.get("FAST_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def move_to_repospanner(self, session, name, namespace, user, region):
-    """ Move a repository to a repoSpanner region.
-    """
+    """Move a repository to a repoSpanner region."""
     project = pagure.lib.query._get_project(
         session, namespace=namespace, name=name, user=user
     )
@@ -703,8 +707,7 @@ def move_to_repospanner(self, session, name, namespace, user, region):
 @conn.task(queue=pagure_config.get("FAST_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def refresh_pr_cache(self, session, name, namespace, user, but_uids=None):
-    """ Refresh the merge status cached of pull-requests.
-    """
+    """Refresh the merge status cached of pull-requests."""
     project = pagure.lib.query._get_project(
         session, namespace=namespace, name=name, user=user
     )
@@ -719,8 +722,7 @@ def refresh_pr_cache(self, session, name, namespace, user, but_uids=None):
 def rebase_pull_request(
     self, session, name, namespace, user, requestid, user_rebaser
 ):
-    """ Rebase a pull-request.
-    """
+    """Rebase a pull-request."""
     project = pagure.lib.query._get_project(
         session, namespace=namespace, name=name, user=user
     )
@@ -755,8 +757,7 @@ def merge_pull_request(
     user_merger,
     delete_branch_after=False,
 ):
-    """ Merge pull-request.
-    """
+    """Merge pull-request."""
     project = pagure.lib.query._get_project(
         session, namespace=namespace, name=name, user=user
     )
@@ -805,8 +806,7 @@ def merge_pull_request(
 def add_file_to_git(
     self, session, name, namespace, user, user_attacher, issueuid, filename
 ):
-    """ Add a file to the specified git repo.
-    """
+    """Add a file to the specified git repo."""
     project = pagure.lib.query._get_project(
         session, namespace=namespace, name=name, user=user
     )
@@ -832,11 +832,11 @@ def add_file_to_git(
 @conn.task(queue=pagure_config.get("MEDIUM_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def project_dowait(self, session, name, namespace, user):
-    """ This is a task used to test the locking systems.
+    """This is a task used to test the locking systems.
 
     It should never be allowed to be called in production instances, since that
     would allow an attacker to basically DOS a project by calling this
-    repeatedly. """
+    repeatedly."""
     assert pagure_config.get("ALLOW_PROJECT_DOWAIT", False)
 
     project = pagure.lib.query._get_project(
@@ -854,7 +854,7 @@ def project_dowait(self, session, name, namespace, user):
 @conn.task(queue=pagure_config.get("MEDIUM_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def sync_pull_ref(self, session, name, namespace, user, requestid):
-    """ Synchronize a pull/ reference from the content in the forked repo,
+    """Synchronize a pull/ reference from the content in the forked repo,
     allowing local checkout of the pull-request.
     """
     project = pagure.lib.query._get_project(
@@ -888,7 +888,7 @@ def sync_pull_ref(self, session, name, namespace, user, requestid):
 @conn.task(queue=pagure_config.get("FAST_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def update_pull_request(self, session, pr_uid, username=None):
-    """ Updates a pull-request in the DB once a commit was pushed to it in
+    """Updates a pull-request in the DB once a commit was pushed to it in
     git.
     """
     request = pagure.lib.query.get_request_by_uid(session, pr_uid)
@@ -915,8 +915,7 @@ def update_pull_request(self, session, pr_uid, username=None):
 @conn.task(queue=pagure_config.get("MEDIUM_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def update_checksums_file(self, session, folder, filenames):
-    """ Update the checksums file in the release folder of the project.
-    """
+    """Update the checksums file in the release folder of the project."""
 
     sha_file = os.path.join(folder, "CHECKSUMS")
     new_file = not os.path.exists(sha_file)
@@ -955,7 +954,7 @@ def update_checksums_file(self, session, folder, filenames):
 @conn.task(queue=pagure_config.get("FAST_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def commits_author_stats(self, session, repopath):
-    """ Returns some statistics about commits made against the specified
+    """Returns some statistics about commits made against the specified
     git repository.
     """
 
@@ -1018,7 +1017,7 @@ def commits_author_stats(self, session, repopath):
 @conn.task(queue=pagure_config.get("FAST_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def commits_history_stats(self, session, repopath):
-    """ Returns the evolution of the commits made against the specified
+    """Returns the evolution of the commits made against the specified
     git repository.
     """
 
@@ -1044,7 +1043,7 @@ def commits_history_stats(self, session, repopath):
 @conn.task(queue=pagure_config.get("MEDIUM_CELERY_QUEUE", None), bind=True)
 @pagure_task
 def link_pr_to_ticket(self, session, pr_uid):
-    """ Link the specified pull-request against the ticket(s) mentioned in
+    """Link the specified pull-request against the ticket(s) mentioned in
     the commits of the pull-request
 
     """
@@ -1232,7 +1231,7 @@ def git_garbage_collect(self, session, repopath):
 def generate_archive(
     self, session, project, namespace, username, commit, tag, name, archive_fmt
 ):
-    """ Generate the archive of the specified project on the specified
+    """Generate the archive of the specified project on the specified
     commit with the given name and archive format.
     Currently only support the following format: gzip and tar.gz
 
@@ -1270,7 +1269,7 @@ def generate_archive(
 @conn.task(queue=pagure_config.get("AUTHORIZED_KEYS_QUEUE", None), bind=True)
 @pagure_task
 def add_key_to_authorized_keys(self, session, ssh_folder, username, sshkey):
-    """ Add the specified key to the the `authorized_keys` file of the
+    """Add the specified key to the the `authorized_keys` file of the
     specified ssh folder.
     """
     if not os.path.exists(ssh_folder):
@@ -1293,7 +1292,7 @@ def add_key_to_authorized_keys(self, session, ssh_folder, username, sshkey):
 @conn.task(queue=pagure_config.get("AUTHORIZED_KEYS_QUEUE", None), bind=True)
 @pagure_task
 def remove_key_from_authorized_keys(self, session, ssh_folder, sshkey):
-    """ Remove the specified key from the the `authorized_keys` file of the
+    """Remove the specified key from the the `authorized_keys` file of the
     specified ssh folder.
     """
     if not os.path.exists(ssh_folder):
diff --git a/pagure/lib/tasks_mirror.py b/pagure/lib/tasks_mirror.py
index 24154ba..776b711 100644
--- a/pagure/lib/tasks_mirror.py
+++ b/pagure/lib/tasks_mirror.py
@@ -8,9 +8,10 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import base64
+import datetime
 import logging
 import os
 import stat
@@ -18,12 +19,11 @@ import struct
 
 import six
 import werkzeug.utils
-
 from celery import Celery
 from cryptography import utils
 from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives.asymmetric import rsa
 from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import rsa
 
 import pagure.lib.query
 from pagure.config import config as pagure_config
@@ -39,7 +39,12 @@ if os.environ.get("PAGURE_BROKER_URL"):  # pragma: no-cover
 elif pagure_config.get("BROKER_URL"):
     broker_url = pagure_config["BROKER_URL"]
 else:
-    broker_url = "redis://%s" % pagure_config["REDIS_HOST"]
+    broker_url = "redis://%s:%d/%d" % (
+        pagure_config["REDIS_HOST"],
+        pagure_config["REDIS_PORT"],
+        pagure_config["REDIS_DB"],
+    )
+
 
 conn = Celery("tasks_mirror", broker=broker_url, backend=broker_url)
 conn.conf.update(pagure_config["CELERY_CONFIG"])
@@ -78,7 +83,7 @@ def _serialize_public_ssh_key(key):
 
 
 def _create_ssh_key(keyfile):
-    """ Create the public and private ssh keys.
+    """Create the public and private ssh keys.
 
     The specified file name will be the private key and the public one will
     be in a similar file name ending with a '.pub'.
@@ -108,8 +113,7 @@ def _create_ssh_key(keyfile):
 @conn.task(queue=pagure_config["MIRRORING_QUEUE"], bind=True)
 @pagure_task
 def setup_mirroring(self, session, username, namespace, name):
-    """ Setup the specified project for mirroring.
-    """
+    """Setup the specified project for mirroring."""
     plugin = pagure.lib.plugins.get_plugin("Mirroring")
     plugin.db_object()
 
@@ -162,8 +166,7 @@ def setup_mirroring(self, session, username, namespace, name):
 @conn.task(queue=pagure_config["MIRRORING_QUEUE"], bind=True)
 @pagure_task
 def teardown_mirroring(self, session, username, namespace, name):
-    """ Stop the mirroring of the specified project.
-    """
+    """Stop the mirroring of the specified project."""
     plugin = pagure.lib.plugins.get_plugin("Mirroring")
     plugin.db_object()
 
@@ -191,8 +194,7 @@ def teardown_mirroring(self, session, username, namespace, name):
 @conn.task(queue=pagure_config["MIRRORING_QUEUE"], bind=True)
 @pagure_task
 def mirror_project(self, session, username, namespace, name):
-    """ Does the actual mirroring of the specified project.
-    """
+    """Does the actual mirroring of the specified project."""
     plugin = pagure.lib.plugins.get_plugin("Mirroring")
     plugin.db_object()
 
@@ -242,7 +244,8 @@ def mirror_project(self, session, username, namespace, name):
             error=True,
             env={"SSHKEY": private_key_file, "GIT_SSH": script_file},
         )
-        log = "Output from the push:\n  stdout: %s\n  stderr: %s" % (
+        log = "Output from the push (%s):\n  stdout: %s\n  stderr: %s" % (
+            datetime.datetime.utcnow().isoformat(),
             stdout,
             stderr,
         )
diff --git a/pagure/lib/tasks_services.py b/pagure/lib/tasks_services.py
index 74e10fa..91bce7f 100644
--- a/pagure/lib/tasks_services.py
+++ b/pagure/lib/tasks_services.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import datetime
 import hashlib
@@ -21,7 +21,6 @@ import uuid
 
 import requests
 import six
-
 from celery import Celery
 from celery.signals import after_setup_task_logger
 from celery.utils.log import get_task_logger
@@ -30,10 +29,10 @@ from sqlalchemy.exc import SQLAlchemyError
 
 import pagure.lib.query
 from pagure.config import config as pagure_config
+from pagure.lib.lib_ci import trigger_jenkins_build
 from pagure.lib.tasks_utils import pagure_task
 from pagure.mail_logging import format_callstack
-from pagure.lib.lib_ci import trigger_jenkins_build
-from pagure.utils import split_project_fullname, set_up_logging
+from pagure.utils import set_up_logging, split_project_fullname
 
 # logging.config.dictConfig(pagure_config.get('LOGGING') or {'version': 1})
 _log = get_task_logger(__name__)
@@ -45,7 +44,11 @@ if os.environ.get("PAGURE_BROKER_URL"):  # pragma: no cover
 elif pagure_config.get("BROKER_URL"):
     broker_url = pagure_config["BROKER_URL"]
 else:
-    broker_url = "redis://%s" % pagure_config["REDIS_HOST"]
+    broker_url = "redis://%s:%d/%d" % (
+        pagure_config["REDIS_HOST"],
+        pagure_config["REDIS_PORT"],
+        pagure_config["REDIS_DB"],
+    )
 
 conn = Celery("tasks", broker=broker_url, backend=broker_url)
 conn.conf.update(pagure_config["CELERY_CONFIG"])
@@ -57,7 +60,7 @@ def augment_celery_log(**kwargs):
 
 
 def call_web_hooks(project, topic, msg, urls):
-    """ Sends the web-hook notification. """
+    """Sends the web-hook notification."""
     _log.info("Processing project: %s - topic: %s", project.fullname, topic)
     _log.debug("msg: %s", msg)
 
@@ -117,7 +120,7 @@ def call_web_hooks(project, topic, msg, urls):
 def webhook_notification(
     self, session, topic, msg, namespace=None, name=None, user=None
 ):
-    """ Send webhook notifications about an event on that project.
+    """Send webhook notifications about an event on that project.
 
     :arg session: SQLAlchemy session object
     :type session: sqlalchemy.orm.session.Session
@@ -167,7 +170,7 @@ def log_commit_send_notifications(
     namespace=None,
     username=None,
 ):
-    """ Send webhook notifications about an event on that project.
+    """Send webhook notifications about an event on that project.
 
     :arg session: SQLAlchemy session object
     :type session: sqlalchemy.orm.session.Session
@@ -272,7 +275,7 @@ def load_json_commits_to_db(
     namespace=None,
     username=None,
 ):
-    """ Loads into the database the specified commits that have been pushed
+    """Loads into the database the specified commits that have been pushed
     to either the tickets or the pull-request repository.
 
     """
@@ -409,9 +412,7 @@ def trigger_ci_build(
     pr_uid=None,
 ):
 
-    """ Triggers a new run of the CI system on the specified pull-request.
-
-    """
+    """Triggers a new run of the CI system on the specified pull-request."""
     pagure.lib.plugins.get_plugin("Pagure CI")
 
     if not pr_uid and not project_name:
diff --git a/pagure/lib/tasks_utils.py b/pagure/lib/tasks_utils.py
index 5958e58..f65d7eb 100644
--- a/pagure/lib/tasks_utils.py
+++ b/pagure/lib/tasks_utils.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import gc
 from functools import wraps
@@ -18,14 +18,14 @@ from pagure.config import config as pagure_config
 
 
 def pagure_task(function):
-    """ Simple decorator that is responsible for:
+    """Simple decorator that is responsible for:
     * Adjusting the status of the task when it starts
     * Creating and cleaning up a SQLAlchemy session
     """
 
     @wraps(function)
     def decorated_function(self, *args, **kwargs):
-        """ Decorated function, actually does the work. """
+        """Decorated function, actually does the work."""
         if self is not None:
             try:
                 self.update_state(state="RUNNING")
@@ -47,6 +47,6 @@ def pagure_task(function):
 
 
 def gc_clean():
-    """ Force a run of the garbage collector. """
+    """Force a run of the garbage collector."""
     # https://pagure.io/pagure/issue/2302
     gc.collect()
diff --git a/pagure/login_forms.py b/pagure/login_forms.py
index 2cd8d5c..fe3624f 100644
--- a/pagure/login_forms.py
+++ b/pagure/login_forms.py
@@ -20,7 +20,7 @@
 # pylint: disable=no-init
 
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import wtforms
 
@@ -31,8 +31,7 @@ except ImportError:
 
 
 def same_password(form, field):
-    """ Check if the data in the field is the same as in the password field.
-    """
+    """Check if the data in the field is the same as in the password field."""
     if field.data != form.password.data:
         raise wtforms.validators.ValidationError(
             "Both password fields should be equal"
@@ -40,76 +39,76 @@ def same_password(form, field):
 
 
 class LostPasswordForm(FlaskForm):
-    """ Form to ask for a password change. """
+    """Form to ask for a password change."""
 
     username = wtforms.StringField(
-        'username  <span class="error">*</span>',
+        "username",
         [wtforms.validators.DataRequired()],
     )
 
 
 class ResetPasswordForm(FlaskForm):
-    """ Form to reset one's password in the local database. """
+    """Form to reset one's password in the local database."""
 
     password = wtforms.PasswordField(
-        'Password  <span class="error">*</span>',
+        "Password",
         [wtforms.validators.DataRequired()],
     )
     confirm_password = wtforms.PasswordField(
-        'Confirm password  <span class="error">*</span>',
+        "Confirm password",
         [wtforms.validators.DataRequired(), same_password],
     )
 
 
 class LoginForm(FlaskForm):
-    """ Form to login via the local database. """
+    """Form to login via the local database."""
 
     username = wtforms.StringField(
-        'username  <span class="error">*</span>',
+        "username",
         [wtforms.validators.DataRequired()],
     )
     password = wtforms.PasswordField(
-        'Password  <span class="error">*</span>',
+        "Password",
         [wtforms.validators.DataRequired()],
     )
 
 
 class NewUserForm(FlaskForm):
-    """ Form to add a new user to the local database. """
+    """Form to add a new user to the local database."""
 
     user = wtforms.StringField(
-        'username  <span class="error">*</span>',
+        "username",
         [wtforms.validators.DataRequired()],
     )
     fullname = wtforms.StringField(
         "Full name", [wtforms.validators.Optional()]
     )
     email_address = wtforms.StringField(
-        'Email address  <span class="error">*</span>',
+        "Email address",
         [wtforms.validators.DataRequired(), wtforms.validators.Email()],
     )
     password = wtforms.PasswordField(
-        'Password  <span class="error">*</span>',
+        "Password",
         [wtforms.validators.DataRequired()],
     )
     confirm_password = wtforms.PasswordField(
-        'Confirm password  <span class="error">*</span>',
+        "Confirm password",
         [wtforms.validators.DataRequired(), same_password],
     )
 
 
 class ChangePasswordForm(FlaskForm):
-    """ Form to reset one's password in the local database. """
+    """Form to reset one's password in the local database."""
 
     old_password = wtforms.PasswordField(
-        'Old Password  <span class="error">*</span>',
+        "Old Password",
         [wtforms.validators.DataRequired()],
     )
     password = wtforms.PasswordField(
-        'Password  <span class="error">*</span>',
+        "Password",
         [wtforms.validators.DataRequired()],
     )
     confirm_password = wtforms.PasswordField(
-        'Confirm password  <span class="error">*</span>',
+        "Confirm password",
         [wtforms.validators.DataRequired(), same_password],
     )
diff --git a/pagure/mail_logging.py b/pagure/mail_logging.py
index 449fc63..1218bee 100644
--- a/pagure/mail_logging.py
+++ b/pagure/mail_logging.py
@@ -23,12 +23,11 @@
 Mail handler for logging.
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
+import inspect
 import logging
 import logging.handlers
-
-import inspect
 import socket
 import traceback
 
@@ -45,7 +44,7 @@ except (OSError, ImportError):  # pragma: no cover
 
 
 def format_callstack():
-    """ Format the callstack to find out the stack trace. """
+    """Format the callstack to find out the stack trace."""
     ind = 0
     for ind, frame in enumerate(f[0] for f in inspect.stack()):
         if "__name__" not in frame.f_globals:
@@ -55,7 +54,7 @@ def format_callstack():
             break
 
     def _format_frame(frame):
-        """ Format the frame. """
+        """Format the frame."""
         return '  File "%s", line %i in %s\n    %s' % (frame)
 
     stack = traceback.extract_stack()
@@ -64,7 +63,7 @@ def format_callstack():
 
 
 class ContextInjector(logging.Filter):  # pragma: no cover
-    """ Logging filter that adds context to log records.
+    """Logging filter that adds context to log records.
 
     Filters are typically used to "filter" log records.  They declare a filter
     method that can return True or False.  Only records with 'True' will
@@ -88,7 +87,7 @@ class ContextInjector(logging.Filter):  # pragma: no cover
     """
 
     def filter(self, record):
-        """ Set up additional information on the record object. """
+        """Set up additional information on the record object."""
         current_process = ContextInjector.get_current_process()
         current_hostname = socket.gethostname()
 
@@ -136,7 +135,7 @@ class ContextInjector(logging.Filter):  # pragma: no cover
 
     @staticmethod
     def get_current_process():
-        """ Return the current process (PID). """
+        """Return the current process (PID)."""
         if not psutil:
             return "Could not import psutil"
         return psutil.Process()
@@ -175,8 +174,7 @@ Callstack that lead to the logging statement
 
 
 def get_mail_handler(smtp_server, mail_admin, from_email):
-    """ Set up the handler sending emails for big exception
-    """
+    """Set up the handler sending emails for big exception"""
 
     mail_handler = logging.handlers.SMTPHandler(
         smtp_server, from_email, mail_admin, "Pagure error"
diff --git a/pagure/perfrepo.py b/pagure/perfrepo.py
index 39f1204..de92c47 100644
--- a/pagure/perfrepo.py
+++ b/pagure/perfrepo.py
@@ -9,16 +9,15 @@
 
 """
 
-from __future__ import print_function, unicode_literals, absolute_import
+from __future__ import absolute_import, print_function, unicode_literals
 
-
-import pprint
 import os
+import pprint
 import traceback
 import types
 
-import six
 import pygit2
+import six
 
 try:
     import _pygit2
@@ -145,7 +144,7 @@ class FakeDiffer(six.Iterator):  # pragma: no cover
 class PerfRepo(
     six.with_metaclass(PerfRepoMeta, six.Iterator)
 ):  # pragma: no cover
-    """ An utility class allowing to go around pygit2's inability to be
+    """An utility class allowing to go around pygit2's inability to be
     stable.
 
     """
diff --git a/pagure/pfmarkdown.py b/pagure/pfmarkdown.py
index 8e9a0fe..4f2a3e2 100644
--- a/pagure/pfmarkdown.py
+++ b/pagure/pfmarkdown.py
@@ -19,24 +19,22 @@ Author: Ralph Bean <rbean@redhat.com>
         Pierre-Yves Chibon <pingou@pingoured.fr>
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import logging
+import re
 
 import flask
-
 import markdown.inlinepatterns
-import markdown.preprocessors
 import markdown.postprocessors
+import markdown.preprocessors
 import markdown.util
 import pygit2
-import re
 import six
 
 import pagure.lib.query
 from pagure.config import config as pagure_config
 
-
 try:
     from markdown.inlinepatterns import ImagePattern as ImagePattern
 
@@ -90,10 +88,10 @@ STRIKE_THROUGH_RE = r"~~(.*?)~~"
 
 
 class MentionPattern(markdown.inlinepatterns.Pattern):
-    """ @user pattern class. """
+    """@user pattern class."""
 
     def handleMatch(self, m):
-        """ When the pattern matches, update the text. """
+        """When the pattern matches, update the text."""
         _log.debug("MentionPattern: %s", m.groups())
 
         name = markdown.util.AtomicString(m.group(2))
@@ -113,10 +111,10 @@ class MentionPattern(markdown.inlinepatterns.Pattern):
 
 
 class ExplicitLinkPattern(markdown.inlinepatterns.Pattern):
-    """ Explicit link pattern. """
+    """Explicit link pattern."""
 
     def handleMatch(self, m):
-        """ When the pattern matches, update the text. """
+        """When the pattern matches, update the text."""
         _log.debug("ExplicitLinkPattern: %s", m.groups())
 
         is_fork = m.group(2)
@@ -154,10 +152,10 @@ class ExplicitLinkPattern(markdown.inlinepatterns.Pattern):
 
 
 class CommitLinkPattern(markdown.inlinepatterns.Pattern):
-    """ Commit link pattern. """
+    """Commit link pattern."""
 
     def handleMatch(self, m):
-        """ When the pattern matches, update the text. """
+        """When the pattern matches, update the text."""
         _log.debug("CommitLinkPattern: %s", m.groups())
 
         is_fork = m.group(2)
@@ -238,10 +236,10 @@ class ImplicitIssuePreprocessor(markdown.preprocessors.Preprocessor):
 
 
 class ImplicitIssuePattern(markdown.inlinepatterns.Pattern):
-    """ Implicit issue pattern. """
+    """Implicit issue pattern."""
 
     def handleMatch(self, m):
-        """ When the pattern matches, update the text. """
+        """When the pattern matches, update the text."""
         _log.debug("ImplicitIssuePattern: %s", m.groups())
         idx = markdown.util.AtomicString(m.group(2))
         text = "#%s" % idx
@@ -280,10 +278,10 @@ class ImplicitIssuePattern(markdown.inlinepatterns.Pattern):
 
 
 class ImplicitPRPattern(markdown.inlinepatterns.Pattern):
-    """ Implicit pull-request pattern. """
+    """Implicit pull-request pattern."""
 
     def handleMatch(self, m):
-        """ When the pattern matches, update the text. """
+        """When the pattern matches, update the text."""
         _log.debug("ImplicitPRPattern: %s", m.groups())
         idx = markdown.util.AtomicString(m.group(2))
         text = "PR#%s" % idx
@@ -309,10 +307,10 @@ class ImplicitPRPattern(markdown.inlinepatterns.Pattern):
 
 
 class ImplicitCommitPattern(markdown.inlinepatterns.Pattern):
-    """ Implicit commit pattern. """
+    """Implicit commit pattern."""
 
     def handleMatch(self, m):
-        """ When the pattern matches, update the text. """
+        """When the pattern matches, update the text."""
         _log.debug("ImplicitCommitPattern: %s", m.groups())
 
         githash = markdown.util.AtomicString(m.group(2))
@@ -332,10 +330,10 @@ class ImplicitCommitPattern(markdown.inlinepatterns.Pattern):
 
 
 class StrikeThroughPattern(markdown.inlinepatterns.Pattern):
-    """ ~~striked~~ pattern class. """
+    """~~striked~~ pattern class."""
 
     def handleMatch(self, m):
-        """ When the pattern matches, update the text. """
+        """When the pattern matches, update the text."""
         _log.debug("StrikeThroughPattern: %s", m.groups())
 
         text = markdown.util.AtomicString(m.group(2))
@@ -346,10 +344,10 @@ class StrikeThroughPattern(markdown.inlinepatterns.Pattern):
 
 
 class AutolinkPattern2(markdown.inlinepatterns.Pattern):
-    """ Return a link Element given an autolink (`<http://example/com>`). """
+    """Return a link Element given an autolink (`<http://example/com>`)."""
 
     def handleMatch(self, m):
-        """ When the pattern matches, update the text.
+        """When the pattern matches, update the text.
 
         :arg m: the matched object
 
@@ -368,7 +366,7 @@ class AutolinkPattern2(markdown.inlinepatterns.Pattern):
 
 
 class ImagePatternLazyLoad(ImagePattern):
-    """ Customize the image element matched for lazyloading. """
+    """Customize the image element matched for lazyloading."""
 
     def handleMatch(self, m, *args):
         out = super(ImagePatternLazyLoad, self).handleMatch(m, *args)
@@ -516,7 +514,7 @@ def makeExtension(*arg, **kwargs):
 
 
 def _issue_exists(user, namespace, repo, idx):
-    """ Utility method checking if a given issue exists. """
+    """Utility method checking if a given issue exists."""
 
     repo_obj = pagure.lib.query.get_authorized_project(
         flask.g.session, project_name=repo, user=user, namespace=namespace
@@ -535,7 +533,7 @@ def _issue_exists(user, namespace, repo, idx):
 
 
 def _pr_exists(user, namespace, repo, idx):
-    """ Utility method checking if a given PR exists. """
+    """Utility method checking if a given PR exists."""
     repo_obj = pagure.lib.query.get_authorized_project(
         flask.g.session, project_name=repo, user=user, namespace=namespace
     )
@@ -553,7 +551,7 @@ def _pr_exists(user, namespace, repo, idx):
 
 
 def _commit_exists(user, namespace, repo, githash):
-    """ Utility method checking if a given commit exists. """
+    """Utility method checking if a given commit exists."""
     repo_obj = pagure.lib.query.get_authorized_project(
         flask.g.session, project_name=repo, user=user, namespace=namespace
     )
@@ -617,7 +615,7 @@ def _obj_anchor_tag(user, namespace, repo, obj, text):
 
 
 def _get_ns_repo_user():
-    """ Return the namespace, repo, user corresponding to the given request
+    """Return the namespace, repo, user corresponding to the given request
 
     :return: A tuple of three string corresponding to namespace, repo, user
     :rtype: tuple(str, str, str)
diff --git a/pagure/proxy.py b/pagure/proxy.py
index 6b588b0..fdb385d 100644
--- a/pagure/proxy.py
+++ b/pagure/proxy.py
@@ -26,7 +26,7 @@ redirects are using ``https``.
 Source: http://flask.pocoo.org/snippets/35/ by Peter Hansen
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 
 class ReverseProxied(object):  # pragma: no cover
diff --git a/pagure/static/emoji/emojicomplete.js b/pagure/static/emoji/emojicomplete.js
index 3af10fe..0c14404 100644
--- a/pagure/static/emoji/emojicomplete.js
+++ b/pagure/static/emoji/emojicomplete.js
@@ -52,6 +52,6 @@ emoji_complete = function(json_url, folder) {
     maxCount: 10
   }
   ],{
-    footer: '<a href="http://www.emoji.codes" target="_blank">Browse All<span class="arrow">&raquo;</span></a>'
+    footer: '<a href="https://github.com/markdown-templates/markdown-emojis" target="_blank">Browse All<span class="arrow">&raquo;</span></a>'
   });
 };
diff --git a/pagure/static/issue_ev.js b/pagure/static/issue_ev.js
index 6289f2b..09ae5f9 100644
--- a/pagure/static/issue_ev.js
+++ b/pagure/static/issue_ev.js
@@ -172,7 +172,7 @@ add_comment = function(data, username) {
     + '              <button class="reply btn btn-outline-primary border-0" type="button"'
     + '                  data-comment="' + data.comment_id + '"'
     + '                  title="Reply to this comment">'
-    + '                <span class="fa fa-share-square-o" title="Reply to this comment"></span>'
+    + '                <span class="fa fa-reply" title="Reply to this comment"></span>'
     + '              </a>';
     if ( data.comment_user == username) {
 
@@ -264,7 +264,7 @@ update_issue = function(data, _roadmap_url) {
       }
     } else if (_f == 'content'){
       var field = $('#comment-0').parent().find('.comment_body');
-      field.html('<p>' + data.issue.content + '</p>');
+      field.html('<p>' + data.content_updated + '</p>');
     } else if (_f == 'milestone'){
       var field = $('#milestone_plain');
       var _url = _roadmap_url.replace('-123456789', data.issue.milestone);
diff --git a/pagure/static/pagure.css b/pagure/static/pagure.css
index d57520b..837a9d3 100644
--- a/pagure/static/pagure.css
+++ b/pagure/static/pagure.css
@@ -501,6 +501,11 @@ th[data-sort] {
   background: #ddd;
 }
 
+.commit_branch {
+  font-size: 1em;
+  background: #ababab;
+}
+
 .light_gray_bg {
   background-color:#ccc;
 }
@@ -549,4 +554,4 @@ th[data-sort] {
 
 .api-doc .card-body h2.title {
   display: none;
-}
\ No newline at end of file
+}
diff --git a/pagure/templates/_access_levels_descriptions.html b/pagure/templates/_access_levels_descriptions.html
index 992502e..c4fba2a 100644
--- a/pagure/templates/_access_levels_descriptions.html
+++ b/pagure/templates/_access_levels_descriptions.html
@@ -15,9 +15,9 @@
   These branches are defined here using their name or a pattern and needs to be comma separated. <br />
   Some examples:
     <ul>
-        <li>master,features/*</li>
-        <li>el*</li>
-        <li>master,f*</li>
+        <li>main,features/*</li>
+        <li>epel*</li>
+        <li>rawhide,f*</li>
     </ul>
 </p>
 <p class="justify">
diff --git a/pagure/templates/_formhelper.html b/pagure/templates/_formhelper.html
index 047cf34..4eb4e06 100644
--- a/pagure/templates/_formhelper.html
+++ b/pagure/templates/_formhelper.html
@@ -31,7 +31,7 @@
         {% endif %}
     {% else %}
       <div>
-        <strong>{{ field.label }}</strong>
+        <strong>{{ field.label }} {% if field.errors %} <span class="error">*</span> {% endif %}</strong>
       {% if rightlink %}
       <div class="float-right"><a href="{{rightlink['url']}}">{{rightlink['text']}}</a>
       {% endif %}
@@ -162,7 +162,7 @@
                 <button class="reply btn btn-outline-primary border-0" type="button"
                     data-comment="{{ comment.id }}"
                     title="Reply to this comment">
-                <span class="fa fa-share-square-o"></span>
+                <span class="fa fa-reply"></span>
             </button>
             {% endif %}
             {% if id != 0 and g.fas_user and (g.repo_committer or (
@@ -207,7 +207,7 @@
   <div class="card-footer bg-transparent d-flex align-items-center border-0 p-0">
     {% if comment.edited_on %}
     <div class="issue_actions ml-3">
-      <small>Edited {{ comment.edited_on | humanize }} by {{ comment.editor.username }} </small>
+      <small>Edited {{ comment.edited_on | humanize_tooltip | safe }} by {{ comment.editor.username }} </small>
       </div>
     {% endif %}
 
@@ -253,7 +253,7 @@
             {% if g.fas_user %}
               <a class="reply btn btn-outline-primary border-0 pointer" data-toggle="tooltip"
                   title="Reply to this comment">
-                <span class="fa fa-share-square-o"></span>
+                <span class="fa fa-reply"></span>
               </a>
             {% endif %}
             {% if g.fas_user and (g.repo_committer or g.fas_user.username == pull_request.user.username) %}
@@ -317,7 +317,7 @@
       {% if g.fas_user %}
         <a class="btn btn-outline-secondary border-0 btn-sm reply pointer"
           title="Reply to the initial comment - lose formatting">
-          <i class="fa fa-share-square-o"></i> Reply
+          <i class="fa fa-reply"></i> Reply
         </a>
       {% endif %}
     </div>
@@ -340,7 +340,7 @@
           <a href="{{ attachment[0] }}?raw=1" title="{{attachment[2]}}" data-toggle="tooltip">{{ attachment[2] }}</a>
         </div>
         <div><small>
-          Attached {{ attachment[3] | humanize}}
+          Attached {{ attachment[3] | humanize_tooltip | safe}}
           {% if attachment[4] is not none %}
           <a href="#comment-{{ attachment[4] }}" class="pull-right">View Comment</a>
           {% else %}
diff --git a/pagure/templates/_render_issues.html b/pagure/templates/_render_issues.html
index dea0d16..2cbacbc 100644
--- a/pagure/templates/_render_issues.html
+++ b/pagure/templates/_render_issues.html
@@ -68,9 +68,7 @@
                         issue.date_created | format_datetime
                       }}">{{ issue.date_created | humanize}}</a> by <span title="{{
                       issue.user.html_title }}">{{ issue.user.user }}</span>.
-                      Modified <span data-toggle="tooltip" title="{{
-                        issue.last_updated | format_datetime
-                      }}">{{ issue.last_updated | humanize}}</span>
+                      Modified {{ issue.last_updated | humanize_tooltip | safe }}
                       </small>
                       {% if showproject %}
                       <small class="ml-auto mt-1">
diff --git a/pagure/templates/_render_pullrequests.html b/pagure/templates/_render_pullrequests.html
index 4f3d325..a8b56e7 100644
--- a/pagure/templates/_render_pullrequests.html
+++ b/pagure/templates/_render_pullrequests.html
@@ -64,38 +64,26 @@
                     {% if request.status|lower == 'merged'%}
                         <small>
                             <span class="text-info font-weight-bold">Merged</span>
-                            <span data-toggle="tooltip" title="{{
-                            request.closed_at | format_datetime
-                          }}">{{ request.closed_at | humanize}}</span>.
+                            {{ request.closed_at | humanize_tooltip | safe }}.
 
-                          Opened <span data-toggle="tooltip" title="{{
-                            request.date_created | format_datetime
-                          }}">{{ request.date_created | humanize}}</span> by <span title="{{
+                          Opened {{ request.date_created | humanize_tooltip | safe }} by <span title="{{
                           request.user.html_title }}">{{ request.user.user }}</span>.
 
                         </small>
                     {% elif request.status|lower == 'closed' %}
                         <small>
                             <span class="text-danger font-weight-bold">Cancelled</span>
-                            <span data-toggle="tooltip" title="{{
-                            request.closed_at | format_datetime
-                          }}">{{ request.closed_at | humanize}}</span>.
-                          Opened <span data-toggle="tooltip" title="{{
-                            request.date_created | format_datetime
-                          }}">{{ request.date_created | humanize}}</span> by <span title="{{
+                            {{ request.closed_at | humanize_tooltip | safe }}.
+                          Opened {{ request.date_created | humanize_tooltip | safe }} by <span title="{{
                           request.user.html_title }}">{{ request.user.user }}</span>.
                         </small>
                     {% else %}
                     <small>
                         <span class="text-success font-weight-bold">Opened</span>
-                        <span data-toggle="tooltip" title="{{
-                        request.date_created | format_datetime
-                      }}">{{ request.date_created | humanize}}</span> by <span title="{{
+                        {{ request.date_created | humanize_tooltip | safe }} by <span title="{{
                       request.user.html_title }}">{{ request.user.user }}</span>.
-                      Modified <span data-toggle="tooltip" title="{{
-                        request.updated_on | format_datetime
-                      }}">{{ request.updated_on | humanize}}</span>
-                      </small>
+                      Modified {{ request.updated_on | humanize_tooltip | safe }}
+                    </small>
                     {% endif %}
                     {% if showproject %}
                       <small class="ml-auto mt-1">
diff --git a/pagure/templates/_render_repo.html b/pagure/templates/_render_repo.html
index cfd0bb8..3ed23eb 100644
--- a/pagure/templates/_render_repo.html
+++ b/pagure/templates/_render_repo.html
@@ -56,7 +56,7 @@
       </div>
       <div class="bg-light border-top">
         <span class="pl-2">
-          <small>created {{repo.date_created|humanize}}</small>
+          <small>created {{repo.date_created|humanize_tooltip | safe}}</small>
         </span>
         <div class="pr-2 text-muted float-right text-align-right">
           <span title="Forks" data-toggle="tooltip">
diff --git a/pagure/templates/_repo_renderdiff.html b/pagure/templates/_repo_renderdiff.html
index a092216..aa9a062 100644
--- a/pagure/templates/_repo_renderdiff.html
+++ b/pagure/templates/_repo_renderdiff.html
@@ -164,7 +164,6 @@
                       commit=patchstats["new_id"],
                       prequest=pull_request,
                       index=loop.index,
-                      isprdiff=True,
                       tree_id=diff_commits[0].tree.id)}}
           </div>
           {% endautoescape %}
diff --git a/pagure/templates/board.html b/pagure/templates/board.html
index 26509ae..46a7eb6 100644
--- a/pagure/templates/board.html
+++ b/pagure/templates/board.html
@@ -65,6 +65,9 @@
             {% elif bissue.issue.status == 'Closed' %}
               <span class="fa fa-fw text-danger fa-exclamation-circle pt-1 icon_id"></span>
               <span class="text-danger font-weight-bold id_txt">#{{ bissue.issue.id }}</span>
+            {% endif %}
+            {% if bissue.issue.assignee %}
+            - {{ bissue.issue.assignee.username | avatar(size=20) | safe}}
             {% endif %}
               - {{ bissue.issue.title | truncate(80, False, '...') }}
               </a>
diff --git a/pagure/templates/commit.html b/pagure/templates/commit.html
index 484bf49..4238e0d 100644
--- a/pagure/templates/commit.html
+++ b/pagure/templates/commit.html
@@ -20,7 +20,7 @@
 {# we recognize non-executable file, executable file and symlink #}
 {% set expected_modes = [33188, 33261, 40960] %}
 <div class="row">
-  <div class="col">
+  <div class="col-2">
       {% block overviewtabs %}{{ super() }}{% endblock %}
   </div>
   <div class="col-10">
@@ -37,9 +37,7 @@
               {% else %}
                 Merged and Committed by {{ commit.author | author2user |safe }}
               {% endif %}
-              <span data-toggle="tooltip" title="{{ commit.commit_time | format_ts }}">
-                {{ commit.commit_time | humanize }}
-              </span>
+              {{ commit.commit_time | humanize_tooltip | safe }}
             {% else %}
               <a href="#" id="diff_list_link">{{ diff|count}} file{{'s' if diff|count > 1 }}</a>
               {% if commit.parents | length == 1 %}
@@ -47,13 +45,9 @@
               {% else %}
                 Merged by {{ commit.author | author2user |safe }}
               {% endif %}
-              <span data-toggle="tooltip" title="{{ commit.commit_time | format_ts }}">
-                {{ commit.commit_time | humanize }}
-              </span>,
+              {{ commit.commit_time | humanize_tooltip | safe }},
               Committed by {{ commit.committer | author2user |safe }}
-              <span data-toggle="tooltip" title="{{ commit.commit_time | format_ts }}">
-                {{ commit.commit_time | humanize }}
-              </span>,
+              {{ commit.commit_time | humanize_tooltip | safe }},
             {% endif%}
           </h5>
       </div>
@@ -245,7 +239,7 @@
                         flag.date_created == flag.date_updated -%}
                           Created at {% else -%} Updated at {% endif -%}
                       {{ flag.date_updated }}" class="pull-xs-right">
-                      {{ flag.date_updated | humanize }}</div>
+                      {{ flag.date_updated | humanize_tooltip | safe }}</div>
                 </div>
                 </small>
               </li>
diff --git a/pagure/templates/group_info.html b/pagure/templates/group_info.html
index 36a2f71..1e6088f 100644
--- a/pagure/templates/group_info.html
+++ b/pagure/templates/group_info.html
@@ -21,7 +21,7 @@
           <h3 class="mb-0 font-weight-bold">{{group.display_name }}</h3>
           {% if group.description %}<div>{{ group.description }}</div>{% endif %}
           <div><small>
-              formed {{ group.created |humanize }},
+              formed {{ group.created |humanize_tooltip | safe }},
               administered by <a href="{{ url_for('ui_ns.view_user', username=group.creator.user)}}">{{ group.creator.user }}</a></small>
           </div>
         </div>
diff --git a/pagure/templates/group_list.html b/pagure/templates/group_list.html
index 0bed5bc..4bcb643 100644
--- a/pagure/templates/group_list.html
+++ b/pagure/templates/group_list.html
@@ -45,7 +45,7 @@
               </div>
             </div>
             <div class="bg-light border-top py-1 px-2 mt-0">
-                <small>Formed {{ group.created |humanize }}</small>
+                <small>Formed {{ group.created |humanize_tooltip | safe }}</small>
               <div class="float-right text-muted">
                 <span title="{{projectstring(plural=True)}}" data-toggle="tooltip" class="mr-1">
                   <span class="fa {{projecticon()}} pr-1"></span>
diff --git a/pagure/templates/issue.html b/pagure/templates/issue.html
index 2a2a5fa..ca8f20e 100644
--- a/pagure/templates/issue.html
+++ b/pagure/templates/issue.html
@@ -168,8 +168,7 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
             </div>
             <span class="font-size-09 autogenerated-comment pl-4">{{ comment.comment | markdown | noJS | safe }}</span>
             <div class="text-muted ml-auto">
-                <span title="{{ comment.date_created | format_datetime }}">{{
-                    comment.date_created | humanize }}</span>
+              {{ comment.date_created | humanize_tooltip | safe }}
             </div>
           </div>
         {% else %}
@@ -591,6 +590,9 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
           <div class="py-2 text-uppercase font-size-09">
             Subscribers
             <span class="badge badge-secondary badge-pill font-size-09 ml-1" id="subscribers-count">{{subscribers|count}}</span>
+            <span id="subscribers-toggle" title="Show/Hide subscribers">
+              <span class="fa fa-arrow-circle-down fa-fw fa-1x"></span>
+            </span>
           </div>
           <div class="ml-auto">
               <a href="#" class="btn btn-sm btn-link" id="subcribe-btn"
@@ -604,11 +606,11 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
         </h5>
 
         {% if subscribers %}
-        <div id="subscribers_list" class="p-2">
+        <div id="subscribers_list" class="p-2 hidden">
         {% for subscriber in subscribers %}
             <a href="{{ url_for('ui_ns.view_user', username=subscriber)
             }}" title="{{ subscriber }}" id="sub-avatar-{{subscriber}}">{{
-              subscriber |avatar(size=30, css_class="pb-1") | safe
+              subscriber |avatar(size=30, css_class="pb-1", src_tag="data-src") | safe
             }}</a>
         {% endfor %}
         </div>
@@ -638,7 +640,7 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
                   namespace=pr.project.namespace,
                   requestid=pr.id) }}">#{{pr.id}}</a>
               {{ pr.status if pr.status != 'Open' else 'Last updated'
-                }} {{ pr.updated_on | humanize }}
+                }} {{ pr.updated_on | humanize_tooltip | safe }}
             </li>
             {% endfor %}
           </ul>
@@ -659,7 +661,7 @@ namespace=repo.namespace, repo=repo.name, issueid=issueid)
   var UPLOAD_URL = "{{ url_for('ui_ns.upload_issue', repo=repo.name, username=username, namespace=repo.namespace, issueid=issue.id) }}";
 </script>
 
-<script type="text/javascript" nonce="{{ g.nonce }}"src="{{
+<script nonce="{{ g.nonce }}" src="{{
   url_for('static', filename='vendor/jquery.textcomplete/jquery.textcomplete.min.js') }}?version={{ g.version}}"></script>
 <script type="text/javascript" nonce="{{ g.nonce }}" src="{{
   url_for('static', filename='vendor/emojione/emojione.min.js') }}?version={{ g.version}}"></script>
@@ -817,6 +819,16 @@ $(document).ready(function() {
     return false;
   };
 
+  $('#subscribers-toggle').click(function(event){
+    var _el = $('#subscribers_list');
+    if (! _el.is(':visible')){
+      $('#subscribers-toggle').html('<span class="fa fa-arrow-circle-up fa-fw fa-1x">');
+    } else {
+      $('#subscribers-toggle').html('<span class="fa fa-arrow-circle-down fa-fw fa-1x">');
+    }
+    _el.toggle();
+  });
+
   {% if g.repo_user %}
   $('#closeticket').click(function(event){
     event.preventDefault();
diff --git a/pagure/templates/login/login.html b/pagure/templates/login/login.html
index a65b10a..e209c40 100644
--- a/pagure/templates/login/login.html
+++ b/pagure/templates/login/login.html
@@ -18,11 +18,13 @@
         <input class="btn btn-primary btn-block mt-4" type="submit" value="Login">
         {{ form.csrf_token }}
       </form>
+      {% if config.get('ALLOW_USER_REGISTRATION', True) %}
       <div>
         <a class="btn btn-link btn-block" href="{{url_for('ui_ns.new_user') }}">
           Create a new account
         </a>
       </div>
+      {% endif %}
     </div>
   </div>
 </div>
diff --git a/pagure/templates/master.html b/pagure/templates/master.html
index 8a50cd8..70524b6 100644
--- a/pagure/templates/master.html
+++ b/pagure/templates/master.html
@@ -19,7 +19,7 @@
     <nav class="navbar {{theme.masthead_class}} navbar-expand">
       <div class="container">
         <a href="{{ url_for('ui_ns.index') }}" class="navbar-brand">
-        <img height=40px src="{{ url_for('theme.static', filename='pagure-logo.png') }}?version={{ g.version}}"
+        <img height="40" src="{{ url_for('theme.static', filename='pagure-logo.png') }}?version={{ g.version}}"
              alt="pagure Logo" id="pagureLogo"/>
         </a>
         <ul class="navbar-nav ml-auto">
diff --git a/pagure/templates/pull_request_title.html b/pagure/templates/pull_request_title.html
index ecbfce7..817b77a 100644
--- a/pagure/templates/pull_request_title.html
+++ b/pagure/templates/pull_request_title.html
@@ -51,6 +51,9 @@
               <div id="preview" class="p-1">
               </div>
             </fieldset>
+            {{ render_bootstrap_field(
+              form.branch_to,
+              field_description="branch in which the pull-request should be merged") }}
             <div class="form-control">
               <label for="allow_rebase">Allow rebasing</label>
               <label class="c-input c-checkbox">
diff --git a/pagure/templates/releases.html b/pagure/templates/releases.html
index 3fc1a12..1ca3182 100644
--- a/pagure/templates/releases.html
+++ b/pagure/templates/releases.html
@@ -66,10 +66,10 @@
                     <i class="fa fa-fw fa-tags text-muted"></i> {{tag['tagname']}}
                     </a>
               </div>
-              <div>{{ tag['head_msg'] }} &bull; {{tag['date'] | humanize}}</div>
+              <div>{{ tag['head_msg'] }} &bull; {{tag['date'] | humanize_tooltip | safe}}</div>
             {% else %}
               <div><strong><i class="fa fa-fw fa-tags text-muted"></i> {{tag['tagname']}}</strong></div>
-              <div>{{tag['date'] | humanize}}</div>
+              <div>{{tag['date'] | humanize_tooltip | safe}}</div>
             {% endif %}
           </div>
           <div class="col-xs-auto pr-2">
diff --git a/pagure/templates/repo_info.html b/pagure/templates/repo_info.html
index 15609c6..e8ec2e0 100644
--- a/pagure/templates/repo_info.html
+++ b/pagure/templates/repo_info.html
@@ -64,15 +64,15 @@
                 namespace=repo.parent.namespace)}}">
                 {{ repo.parent.fullname }}
               </a>
-              {{repo.date_created|humanize}}
+              {{repo.date_created|humanize_tooltip | safe}}
             </span>
             {% elif repo.is_fork and not repo.parent %}
             <span class="text-muted">
-                Forked from a deleted repository {{repo.date_created|humanize}}
+                Forked from a deleted repository {{repo.date_created|humanize_tooltip | safe}}
             </span>
             {% endif %}
             {% if not repo.is_fork %}
-            <div class="text-muted">Created {{repo.date_created|humanize}}</div>
+            <div class="text-muted">Created {{repo.date_created|humanize_tooltip | safe}}</div>
             <div class="text-muted">
             Maintained by
             <a href="{{ url_for('ui_ns.view_user', username=repo.user.user)}}">
@@ -225,7 +225,7 @@
                       <div class="dropdown-menu dropdown-menu-right" id="maintainers_dropdown">
                           <div>
                               <div class="media p-2">
-                                  <img class=" align-self-center mr-3" src="{{ repo.user.default_email | avatar_url }}" width=50px height=50px>
+                                  <img class=" align-self-center mr-3" src="{{ repo.user.default_email | avatar_url }}" width="50" height="50">
                                   <div class="media-body align-self-center">
                                     <h4 class="my-0 font-weight-bold">
                                       <a href="{{ url_for('ui_ns.view_user', username=repo.user.user)}}">{{ repo.user.user }}</a>
@@ -237,7 +237,7 @@
                               {% for access in repo.contributors %}
                                 {% for user in repo.contributors[access] %}
                                   <div class="media p-2">
-                                      <img class=" align-self-center mr-3" src="{{ user.default_email | avatar_url }}" width=50px height=50px>
+                                      <img class=" align-self-center mr-3" src="{{ user.default_email | avatar_url }}" width="50" height="50">
                                       <div class="media-body align-self-center">
                                         <h4 class="my-0 font-weight-bold">
                                           <a href="{{ url_for('ui_ns.view_user', username=user.user)}}">{{ user.user }}</a>
@@ -279,12 +279,13 @@
                           namespace=repo.namespace,
                           commitid=commit.hex, branch=branchname) }}"
                           class="notblue">
-                          <code class="py-1 px-2 font-weight-bold commit_hash">{{ commit.hex|short }}</code>
+                          <code class="py-1 px-2 font-weight-bold commit_branch">{{ branchname }}</code><code
+                           class="py-1 px-2 font-weight-bold commit_hash">{{ commit.hex|short }}</code>
                           <span class="font-weight-bold">{{ commit.message.split('\n')[0] }}</span>
                         </a>
                       </div>
                       <div class="ml-auto">
-                          <span><span class="font-weight-bold">{{ commit.author | author2avatar(20) | safe }} {{commit.author.name}}</span> committed {{ commit.commit_time|humanize }}</span>
+                          <span><span class="font-weight-bold">{{ commit.author | author2avatar(20) | safe }} {{commit.author.name}}</span> committed {{ commit.commit_time|humanize_tooltip | safe }}</span>
                       </div>
                     </div>
                     {% endif %}
@@ -294,7 +295,7 @@
         <div class="alert {% if category == 'error' %}alert-warning{% else %}alert-info{%endif%}" role="alert">
           <p>This repo is brand new and meant to be mirrored from {{
                 repo.mirrored_from }} !</p>
-          <p>Mirrored projects are refreshed regularly, please seat tight, code will
+          <p>Mirrored projects are refreshed regularly. Please sit tight, code will
           come land soon!</p>
         </div>
     {% elif g.repo_obj and (g.repo_obj.is_empty or g.repo_obj.head_is_unborn) %}
@@ -307,8 +308,8 @@ git push -u origin {{ g.repo_obj | get_default_branch or "<your main branch>"}}<
 
               <p>If you have not created your git repo yet:</p>
               <pre>git clone {{ config.get('GIT_URL_SSH') }}{{ repo.path }}
-git checkout -b {{ g.repo_obj | get_default_branch or "<your main branch>"}}
 cd {{ repo.name }}
+git checkout -b {{ g.repo_obj | get_default_branch or "<your main branch>"}}
 touch README.rst
 git add README.rst
 git commit -m "Add README file"
diff --git a/pagure/templates/repo_master.html b/pagure/templates/repo_master.html
index 75ed514..91b7830 100644
--- a/pagure/templates/repo_master.html
+++ b/pagure/templates/repo_master.html
@@ -74,11 +74,11 @@
                 namespace=repo.parent.namespace)}}">
                 {{ repo.parent.fullname }}
               </a>
-              {{repo.date_created|humanize}}
+              {{repo.date_created|humanize_tooltip | safe}}
             </span>
             {% elif repo.is_fork and not repo.parent %}
             <span class="text-muted">
-                Forked from a deleted repository {{repo.date_created|humanize}}
+                Forked from a deleted repository {{repo.date_created|humanize_tooltip | safe}}
             </span>
             {% endif %}
         </div>
@@ -185,7 +185,7 @@
           <span>Clone</span>
         </a>
         <div class="dropdown-menu dropdown-menu-right">
-          <div class="m-3" id="source-dropdown" class="pointer">
+          <div class="m-3" id="source-dropdown">
             <div>
               <h5><strong>Source Code</strong></h5>
               {% if g.authenticated and g.repo_committer %}
@@ -321,7 +321,7 @@ $(".select-on-focus").on("focus", function() {
 
 {% if g.authenticated and not g.repo_obj.is_empty %}
 
-{% if g.repo_committer %}
+{% if g.repo_committer or g.repo_forked %}
 var _cnt = 0;
 
 function process_task_results(_data, callback) {
@@ -417,6 +417,8 @@ $("#pr-button").one("click",
       _b.show();
       $("#pr-dropdown-mainlist .pr-dropdown-spinner").hide();
   })
+  {% else %}
+  $("#pr-dropdown-mainlist .pr-dropdown-spinner").hide();
   {% endif %}
 
   var data = {
diff --git a/pagure/templates/repo_new_pull_request.html b/pagure/templates/repo_new_pull_request.html
index 17b09a2..7687df1 100644
--- a/pagure/templates/repo_new_pull_request.html
+++ b/pagure/templates/repo_new_pull_request.html
@@ -210,7 +210,7 @@
               <input checked id="allow_rebase" name="allow_rebase" type="checkbox" value="y">
             </label>
             <small class="text-muted">
-              Let the maintainer of the target project to rebase the pull-request
+              Allow the maintainer of the target project to rebase the pull-request
             </small>
           </div>
         </div>
diff --git a/pagure/templates/repo_pull_request.html b/pagure/templates/repo_pull_request.html
index 2581c5c..9fcd1e5 100644
--- a/pagure/templates/repo_pull_request.html
+++ b/pagure/templates/repo_pull_request.html
@@ -97,7 +97,7 @@
               {% if pull_request.project_from.is_fork -%}
                 {{ pull_request.project_from.user.user }}/
               {%- endif -%}
-              {{ pull_request.project_from.name }}  
+              {{ pull_request.project_from.name }}
             </span>
             {% elif pull_request.project_from is none and pull_request.remote_git is none %}
             <span class="badge badge-light badge-pill border border-seconday font-1em">
@@ -132,7 +132,8 @@
 
 
       </h4>
-    <div class="ml-auto d-flex">
+    <div class="ml-auto d-flex flex-column">
+      <div class="d-flex justify-content-end">
         {% if pull_request.status == 'Open' %}
           {% if g.authenticated and (g.fas_user.username == pull_request.user.username
             or g.repo_committer) %}
@@ -161,7 +162,7 @@
               </button>
             </form>
           {% endif %}
-          <div class="dropdown float-right ml-1">
+          <div class="dropdown inline ml-1">
             <span class="dropdown dropdown-btn">
               <a href="#" id="merge_dropdown_btn"
                   class="btn btn-outline-secondary btn-sm disabled dropdown-toggle" data-toggle="dropdown">
@@ -236,7 +237,19 @@
             </button>
           </form>
        {% endif %}
+      </div>
+      {% if warning_characters %}
+      <div>
+          <span class="badge badge-danger font-size-09">warning</span>
+        <small>
+          Special characters such as: <a
+              href="https://en.wikipedia.org/wiki/Bidirectional_text">Bidirectional
+              characters</a>, found in this PR.
+        </small>
+      </div>
+      {% endif %}
     </div>
+
   </div>
 
 
@@ -336,7 +349,8 @@
               <div class="col-xs-auto pr-3 text-right">
                   <div class="btn-group">
                     <a href="{{ commit_link }}"
-                      class="btn btn-outline-primary font-weight-bold {{'disabled' if not commit_link}}">
+                      data-commithash="{{ commit.hex }}"
+                      class="btn btn-outline-primary font-weight-bold {{'disabled' if not commit_link}} commithash">
                       <code>{{ commit.hex|short }}</code>
                     </a>
                     <a class="btn btn-outline-primary font-weight-bold {{'disabled' if not commit_link}}" href="{{tree_link}}"><span class="fa fa-file-code-o fa-fw"></span></a>
@@ -396,8 +410,7 @@
             </div>
             <span class="font-size-09 autogenerated-comment pl-4">{{ comment.comment | markdown | noJS | safe }}</span>
             <div class="text-muted ml-auto">
-                <span title="{{ comment.date_created | format_datetime }}">{{
-                    comment.date_created | humanize }}</span>
+              {{ comment.date_created | humanize_tooltip | safe }}
             </div>
           </div>
         {% else %}
@@ -410,6 +423,12 @@
       </form>
     </section>
 
+    <div class="mb-4">
+        <div class="list-group list-group-flush" id="flags">
+        </div>
+    </div>
+
+
     {% if g.authenticated and mergeform %}
     <div class="card mt-5">
     {% if g.authenticated %}
@@ -459,7 +478,7 @@
         </form>
 
           {% endif %}
-  </div>
+    </div>
       <div class="small">
         <p>
           Pull this pull-request locally
@@ -471,9 +490,8 @@
         <pre id="local_pull_info" class="hidden">git fetch {{ config.get('GIT_URL_GIT') }}{{ repo.fullname }}.git refs/pull/{{ pull_request.id }}/head:pr{{ pull_request.id }}</pre>
       </div>
     {% endif %}
-
-
   </div>
+
   <div class="col-md-4">
     <div>
     <div class="mb-4">
@@ -581,7 +599,6 @@
         {% endif %}
     </div>
 
-
     {% if pull_request.flags %}
     <div class="mb-4">
         <h5 class="d-flex align-items-center font-weight-bold border-bottom">
@@ -622,6 +639,9 @@
               <div class="py-2 text-uppercase font-size-09">
                 Subscribers
                 <span class="badge badge-secondary badge-pill font-size-09 ml-1" id="subscribers-count">{{subscribers|count}}</span>
+                <span title="Show/Hide subscribers" id="subscribers-toggle">
+                  <span class="fa fa-arrow-circle-down fa-fw fa-1x"</span>
+                </span>
               </div>
               <div class="ml-auto">
                   <a href="#" class="btn btn-sm btn-link" id="subcribe-btn"
@@ -633,13 +653,13 @@
                 </a>
                 </div>
             </h5>
-              {% if subscribers %}
 
-            <div id="subscribers_list" class="p-2">
+            {% if subscribers %}
+            <div id="subscribers_list" class="p-2 hidden">
             {% for subscriber in subscribers %}
                 <a href="{{ url_for('ui_ns.view_user', username=subscriber)
                 }}" title="{{ subscriber }}" id="sub-avatar-{{subscriber}}">{{
-                  subscriber |avatar(size=30, css_class="pb-1") | safe
+                  subscriber |avatar(size=30, css_class="pb-1", src_tag="data-src") | safe
                 }}</a>
             {% endfor %}
 
@@ -883,7 +903,110 @@ function show_merge_status(){
 }
 {% endif %}
 
+var statusesLabels = {{ flag_statuses_labels|safe }}
+
 $(document).ready(function() {
+
+  $('#subscribers-toggle').click(function(event){
+    var _el = $('#subscribers_list');
+    if (! _el.is(':visible')){
+      $('#subscribers-toggle').html('<span class="fa fa-arrow-circle-up fa-fw fa-1x">');
+    } else {
+      $('#subscribers-toggle').html('<span class="fa fa-arrow-circle-down fa-fw fa-1x">');
+    }
+    _el.toggle();
+  });
+
+  {# Show latest tag #}
+  {% if pull_request.commit_stop %};
+    var url = '{{ url_for("api_ns.api_commit_flags",
+                  repo=repo.name,
+                  username=repo.user.user if repo.is_fork else none,
+                  namespace=repo.namespace,
+                  commit_hash=pull_request.commit_stop) }}'
+    var commitUrl = '{{ url_for("ui_ns.view_commit",
+                        repo=repo.name,
+                        username=username,
+                        namespace=repo.namespace,
+                        commitid=pull_request.commit_stop) }}';
+    $.ajax({
+      url: url,
+      type: 'GET',
+      dataType: 'json',
+      success: function(res) {
+        var statusesLabels = {{ flag_statuses_labels|safe }};
+        var flags_html = '';
+
+        for (var i in res['flags']) {
+          var f = res['flags'][i];
+          var t = f.date_created == f.date_updated ? "Created at": "Updated at";
+          var d = new Date(f.date_updated * 1000);
+          var html = '<a href="' + f.url + '" class="list-group-item list-group-item-action border-0 pl-2 pr-2">'
+            + ' <div>'
+            + '    <span class="font-weight-bold">' + f.username
+            + '    </span>'
+            + '    <div class="float-right">'
+            + '      <span class="badge ' + statusesLabels[f.status] + ' font-size-09">' + f.status
+            + '      </span>'
+            + '    </div>'
+            + '  </div>'
+            + '  <small><div class="clearfix">'
+            + '      <span>' + f.comment + '</span>'
+            + '      <div title="' + t + ' ' + d.toUTCString() + '" class="float-right">'
+            + '      ' + d.toUTCString() + '</div>'
+            + '  </div>'
+            + '  </small>'
+            + '</a>';
+            flags_html += html;
+        }
+        $("#flags").html(flags_html);
+      }
+    });
+  {% endif %}
+
+  {# Show flags in commit list #}
+  $(".commithash").each(function(idx, item) {
+    var url = '{{ url_for("api_ns.api_commit_flags",
+                  repo=repo.name,
+                  username=repo.user.user if repo.is_fork else none,
+                  namespace=repo.namespace,
+                  commit_hash='COMMIT_HASH') }}'
+    var commitUrl = '{{ url_for("ui_ns.view_commit",
+                        repo=repo.name,
+                        username=username,
+                        namespace=repo.namespace,
+                        commitid="COMMIT_HASH") }}';
+    var commithash= $(item).attr('data-commithash');
+    url = url.replace("COMMIT_HASH", commithash);
+    commitUrl = commitUrl.replace("COMMIT_HASH", commithash);
+    $.ajax({
+      url: url,
+      type: 'GET',
+      dataType: 'json',
+      success: function(res) {
+        var statuses = {}
+        for (var i in res['flags']) {
+          var f = res['flags'][i]
+          if (!(f['status'] in statuses)) {
+            statuses[f['status']] = []
+          }
+          statuses[f['status']].push(f)
+        }
+        var html = ''
+        var sortedStatuses = Object.keys(statuses).sort()
+        for (var i in sortedStatuses) {
+          s = sortedStatuses[i]
+          numStatuses = statuses[s].length
+          html += '<a href="' + commitUrl + '" title="' + numStatuses
+          html += ' ' + s + ' flag' + (numStatuses > 1 ? 's' : '')
+          html += '" class="btn ' + statusesLabels[s].replace('badge', 'btn-outline') + '">'
+          html += statuses[s].length + '</a>\n'
+        }
+        $(html).insertBefore(item);
+      }
+    });
+  });
+
   $('#merge_btn').click(function() {
       return confirm('Confirm merging this pull-request');
   });
@@ -1387,7 +1510,7 @@ $(document).ready(function () {
             _countlabel.text(_count+1)
             var _html = '<a href="/user/' + data.user + '"'
                       + 'title="'+data.user+'" id="sub-avatar-'+data.user+'">'
-                      + '<img src="'+data.avatar_url+'" class="pb-1"></a>';
+                      + '<img src="'+data.avatar_url+'" class="pb-1 lazyload"></a>';
             $('#subscribers_list').prepend(_html);
           } else {
             _btn.text('Subscribe');
diff --git a/pagure/templates/repo_stats.html b/pagure/templates/repo_stats.html
index 1f67477..6d04661 100644
--- a/pagure/templates/repo_stats.html
+++ b/pagure/templates/repo_stats.html
@@ -60,6 +60,7 @@ var view_commits_url = "{{ url_for('ui_ns.view_commits',
                     author='---') }}";
 
 {% if g.issues_enabled %}
+nweeks = 53;
 issues_history_stats_plot_call = function() {
   $("#commiter_list").hide();
   $(".commit_trend").hide();
@@ -67,7 +68,7 @@ issues_history_stats_plot_call = function() {
     'api_ns.api_view_issues_history_detailed_stats',
     repo=g.repo.name,
     username=username,
-    namespace=g.repo.namespace) }}";
+    namespace=g.repo.namespace) }}?weeks_range=" + nweeks;
   var _s = $("#data_stats_spinner");
   _s.html(
     "<img id='spinnergif' src='{{ url_for('static', filename='images/spinner.gif') }}?version={{ g.version}}'>"
diff --git a/pagure/templates/settings.html b/pagure/templates/settings.html
index 5515035..f235e0c 100644
--- a/pagure/templates/settings.html
+++ b/pagure/templates/settings.html
@@ -19,8 +19,8 @@
           <h5 class="pl-2 font-weight-bold text-muted">Project Settings</h5>
           <a class="nav-item nav-link active" id="projectdetails" data-toggle="tab"
                 href="#projectdetails-tab" role="tab" aria-controls="projectdetails" aria-selected="true">Project Details</a>
-          <a class="nav-item nav-link" id="defaultbranch" data-toggle="tab"
-                href="#defaultbranch-tab" role="tab" aria-controls="defaultbranch">Default Branch</a>
+          <a class="nav-item nav-link" id="gitbranch" data-toggle="tab"
+                href="#gitbranch-tab" role="tab" aria-controls="gitbranch">Git Branches</a>
 
           {% if config.get('WEBHOOK', False) %}
           <a class="nav-item nav-link" id="privatewebhookkey" data-toggle="tab"
@@ -179,22 +179,9 @@
               </div>
             </div>
           </div>
-          <div class="tab-pane fade" id="defaultbranch-tab" role="tabpanel" aria-labelledby="defaultbranch-tab">
-            <h3 class="font-weight-bold mb-3">
-              Default Branch
-            </h3>
-            <div class="row">
-              <div class="col">
-                <form action="{{ url_for('ui_ns.change_ref_head',
-                    repo=repo.name,
-                    username=username,
-                    namespace=repo.namespace) }}" method="post">
-                  {{ branches_form.csrf_token }}
-                  {{ branches_form.branches(class_="c-select") }}
-                  <input class="btn btn-primary" type="submit" value="Make Default"/>
-                </form>
-              </div>
-            </div>
+
+          <div class="tab-pane fade" id="gitbranch-tab" role="tabpanel" aria-labelledby="gitbranch-tab">
+              {% include 'settings_git_branches.html' %}
           </div>
 
           {% if config.get('WEBHOOK', False) %}
diff --git a/pagure/templates/settings_api_keys.html b/pagure/templates/settings_api_keys.html
index 6c68b77..4323f51 100644
--- a/pagure/templates/settings_api_keys.html
+++ b/pagure/templates/settings_api_keys.html
@@ -36,9 +36,6 @@
       These are your personal tokens; they are not visible to the other
       admins of this repository.
     </p>
-    <p>
-      These keys are valid for <span class="strong">60</span> days.
-    </p>
     <p>
       These keys are private to your project, make sure to store in a safe
       place and do not share it.
diff --git a/pagure/templates/settings_git_branches.html b/pagure/templates/settings_git_branches.html
new file mode 100644
index 0000000..e5aaeda
--- /dev/null
+++ b/pagure/templates/settings_git_branches.html
@@ -0,0 +1,195 @@
+
+<h3 class="font-weight-bold mb-3">
+  Default Branch
+</h3>
+<div class="row">
+  <div class="col">
+    <form action="{{ url_for('ui_ns.change_ref_head',
+        repo=repo.name,
+        username=username,
+        namespace=repo.namespace) }}" method="post">
+      {{ branches_form.csrf_token }}
+      {{ branches_form.branches(class_="c-select") }}
+      <input class="btn btn-primary" type="submit" value="Make Default"/>
+    </form>
+  </div>
+</div>
+
+<div class="row p-t-1">
+  <div class="col-sm-12"></div>
+</div>
+
+<h3 class="font-weight-bold mb-3">
+  Git Branch Alias
+</h3>
+<div class="row">
+  <div class="col">
+    <div class="form-group">
+      <div class="row">
+        <div class="col-sm-5" >
+          Alias name
+        </div>
+
+        <div class="col-sm-5" >
+          Alias To (existing reference/branch)
+        </div>
+
+      </div>
+    </div>
+    <div class="form-group settings-field-rows" id="git-alias-list">
+      <form action="{{ url_for('api_ns.api_drop_git_alias',
+            repo=repo.name,
+            username=username,
+            namespace=repo.namespace) }}" class="add-alias-form hidden blank-field" method="post">
+        <div class="row">
+          <div class="col-sm-5" >
+            <input type="text" name="alias_from" value="" class="form-control"/>
+          </div>
+          <div class="col-sm-5" >
+            <input type="text" name="alias_to" value="" class="form-control"/>
+          </div>
+
+          <div class="col-sm-1">
+            <a class="btn btn-outline-info create-field-row pointer create-alias"
+                title="Create this alias"><i class="fa fa-check"></i></a>
+          </div>
+          <div class="col-sm-1">
+            <a class="btn btn-outline-danger remove-settings-field-row pointer drop-alias"
+                title="Delete this alias"><i class="fa fa-trash"></i></a>
+          </div>
+        </div>
+      </form>
+      {% for alias in branch_aliases %}
+        <form action="{{ url_for('api_ns.api_drop_git_alias',
+              repo=repo.name,
+              username=username,
+              namespace=repo.namespace) }}" class="add-alias-form" method="post">
+          <div class="row">
+            <div class="col-sm-5" >
+              <input type="text" name="alias_from" value="{{ alias | replace('refs/heads/', '') }}"
+                class="form-control" readonly />
+            </div>
+            <div class="col-sm-5" >
+              <input type="text" name="alias_to" value="{{ branch_aliases[alias] | replace('refs/heads/', '') }}"
+                class="form-control" readonly />
+            </div>
+
+            <div class="col-sm-1">
+            </div>
+            <div class="col-sm-1">
+              <a class="btn btn-outline-danger remove-settings-field-row pointer drop-alias"
+                title="Delete this alias"><i class="fa fa-trash"></i></a>
+            </div>
+          </div>
+        </form>
+      {% endfor %}
+    </div>
+    <a class="btn btn-secondary pt-2 btn-sm btn-block add-alias" data-target="#git-alias-list">
+      <i class="fa fa-plus"></i> Add new alias
+    </a>
+  </div>
+</div>
+
+<script type="text/javascript" nonce="{{ g.nonce }}" src="{{
+      url_for('static', filename='vendor/jquery/jquery.min.js') }}?version={{ g.version}}"></script>
+
+<script type="text/javascript" nonce="{{ g.nonce }}">
+
+function set_up_drop_btn() {
+  $('.drop-alias').click(function(e) {
+    _form = $(this).closest('.add-alias-form');
+    data = $(_form).serializeArray();
+    output = {}
+    for (d = 0; d < data.length; d++ ) {
+      output[data[d]["name"]] = data[d]["value"];
+    }
+
+    $.ajax({
+        url: _form.prop('action') ,
+        type: 'POST',
+        contentType: 'application/json; charset=utf-8',
+        data: JSON.stringify(output),
+        dataType: 'json',
+        success: function(res) {
+          _form.hide()
+
+          var _html = '<div class="container pt-2">'
+            + '  <div class="alert alert-info border border-secondary bg-white alert-dismissible" role="alert">'
+            + '      <button type="button" class="close" data-dismiss="alert" aria-label="Close">'
+            + '      <span aria-hidden="true">×</span>'
+            + '      <span class="sr-only">Close</span>'
+            + '    </button>'
+            + '    <div class="text-info font-weight-bold">'
+            + '      <i class="fa fa-fw fa-info-circle"></i>Alias deleted'
+            + '    </div>'
+            + '  </div>'
+            + '</div>';
+          $('.bodycontent').prepend(_html)
+        },
+        error: function(res) {
+          console.log(res);
+          alert('Request failed: ' + res.responseJSON["error"]);
+        }
+    });
+    return false;
+  });
+};
+set_up_drop_btn();
+
+
+function set_up_create_btn() {
+  $('.create-alias').click(function(e) {
+    _form = $(this).closest('.add-alias-form');
+    data = $(_form).serializeArray();
+    output = {}
+    for (d = 0; d < data.length; d++ ) {
+      output[data[d]["name"]] = data[d]["value"];
+    }
+
+    $.ajax({
+        url: "{{ url_for('api_ns.api_new_git_alias',
+            repo=repo.name,
+            username=username,
+            namespace=repo.namespace) }}",
+        type: 'POST',
+        contentType: 'application/json; charset=utf-8',
+        data: JSON.stringify(output),
+        dataType: 'json',
+        success: function(res) {
+          _form.find(".create-alias").hide()
+          _form.find("input").prop("readonly", true);
+
+          var _html = '<div class="container pt-2">'
+            + '  <div class="alert alert-info border border-secondary bg-white alert-dismissible" role="alert">'
+            + '      <button type="button" class="close" data-dismiss="alert" aria-label="Close">'
+            + '      <span aria-hidden="true">×</span>'
+            + '      <span class="sr-only">Close</span>'
+            + '    </button>'
+            + '    <div class="text-info font-weight-bold">'
+            + '      <i class="fa fa-fw fa-info-circle"></i>Alias created'
+            + '    </div>'
+            + '  </div>'
+            + '</div>';
+          $('.bodycontent').prepend(_html)
+
+        },
+        error: function(res) {
+          console.log(res);
+          alert('Request failed: ' + res.responseJSON["error"]);
+        }
+    });
+    return false;
+  });
+};
+
+$('.add-alias').click(function(e) {
+  let target = $(this).attr("data-target");
+  let row = $(target + ".settings-field-rows .blank-field").clone();
+  row.removeClass("hidden");
+  row.removeClass("blank-field");
+  $(target + ".settings-field-rows").append(row);
+  set_up_drop_btn();
+  set_up_create_btn();
+});
+
+</script>
diff --git a/pagure/templates/user_list.html b/pagure/templates/user_list.html
index 1c1b02e..277243a 100644
--- a/pagure/templates/user_list.html
+++ b/pagure/templates/user_list.html
@@ -42,9 +42,7 @@
           </div>
 
           <div class="bg-light border-top py-1 px-2 mt-0">
-              <small>joined <span title="{{
-                user.created | format_datetime }}">{{
-                user.created | humanize }}</span></small>
+              <small>joined {{ user.created | humanize_tooltip | safe }}</small>
             <div class="float-right text-muted">
               <span title="{{projectstring(plural=True)}}" data-toggle="tooltip">
                 <span class="fa {{projecticon()}} pr-1"></span>
diff --git a/pagure/templates/user_settings.html b/pagure/templates/user_settings.html
index 03fd668..f051fb4 100644
--- a/pagure/templates/user_settings.html
+++ b/pagure/templates/user_settings.html
@@ -7,6 +7,7 @@
 {% set tag = "users"%}
 
 {% macro render_email(email, form, validated=True) %}
+{% set random_number = range(0, 256) | random() %}
 <div class="list-group-item {% if not validated %}disabled{% endif %}">
   <span class="fa fa-envelope text-muted"></span> &nbsp;{{ email.email }}
   {% if validated %}
@@ -25,11 +26,11 @@
     </div>
     {% else %}
     <form class="inline" method="POST"
-      action="{{ url_for('ui_ns.set_default_email') }}" id="default_mail">
+      action="{{ url_for('ui_ns.set_default_email') }}" id="default_mail_{{ random_number }}">
       <input type="hidden" value="{{ email.email }}" name="email" />
       {{ form.csrf_token }}
       <a class="float-right p-r-1 btn btn-outline-warning border-0 text-secondary mr-1 pointer submit-btn"
-         data-form-id="default_mail" title="Set as default email address">
+         data-form-id="default_mail_{{ random_number }}" title="Set as default email address">
          <span class="fa fa-star" data-toggle="tooltip"></span>
       </a>
     </form>
@@ -84,7 +85,7 @@
               <fieldset class="form-group text-center">
                 <div>
                   <div class="p-2 mt-2 bg-light border border-secondary"> {{ g.fas_user.username | avatar(80) | safe }} </div>
-                  <a class="btn btn-outline-primary btn-sm mt-1" href="https://www.libravatar.org/account/login/">
+                  <a class="btn btn-outline-primary btn-sm mt-1" href="https://www.libravatar.org/accounts/login/">
                   Change Avatar </a>
                 </div>
               </fieldset>
@@ -153,9 +154,6 @@
               <p>
                 These are your personal tokens; they are not visible to others.
               </p>
-              <p>
-                These keys are valid for <span class="strong">60</span> days.
-              </p>
               <p>
                 These keys are private, make sure to store in a safe place and
                 do not share it.
diff --git a/pagure/templates/userdash_activity.html b/pagure/templates/userdash_activity.html
index 043accf..bed781f 100644
--- a/pagure/templates/userdash_activity.html
+++ b/pagure/templates/userdash_activity.html
@@ -77,7 +77,7 @@
                                 {{render_reponame(log.project)}}
                                 </div>
                                 <div class="ml-auto">
-                                    {{log.date_created|humanize}}
+                                    {{log.date_created|humanize_tooltip | safe}}
                                 </div>
                             </div>
                             <div class="list-group mt-2">
@@ -99,7 +99,7 @@
                                     {{render_reponame(log.project)}}
                                 </div>
                                 <div class="ml-auto">
-                                    {{log.date_created|humanize}}
+                                    {{log.date_created|humanize_tooltip | safe}}
                                 </div>
                             </div>
                             <div class="list-group mt-2">
@@ -113,7 +113,7 @@
                                     {{render_reponame(log.project)}}
                                 </div>
                                 <div class="ml-auto">
-                                    {{log.date_created|humanize}}
+                                    {{log.date_created|humanize_tooltip | safe}}
                                 </div>
                             </div>
                         {% elif self.log_type == 'committed' %}
diff --git a/pagure/templates/userprofile_pullrequests.html b/pagure/templates/userprofile_pullrequests.html
index 046d43b..0e09ed7 100644
--- a/pagure/templates/userprofile_pullrequests.html
+++ b/pagure/templates/userprofile_pullrequests.html
@@ -2,6 +2,7 @@
 
 {% block title %}Pull Requests for {{ username }}{% endblock %}
 
+{% from "_render_repo.html" import pagination_link %}
 {% from "_render_pullrequests.html" import render_pullrequest_row %}
 
 {% block userprofile_content %}
@@ -11,54 +12,49 @@
       <h4 class="mb-0 font-weight-bold">
           Pull Requests for {{ username | avatar(20) | safe }} {{ username }}
       </h4>
+
+      <div class="ml-auto">
+          <span class="btn-group btn-group-sm issues-tagbar" role="group">
+              <a data-togglebutton="pr-type-filed" href="?type=filed&status={{ pr_status }}" id="toggle-open"
+                class="btn {{ 'btn-primary' if pr_type == 'filed' else 'btn-outline-primary' }} btn-sm">PR I filed</a>
+              <a data-togglebutton="pr-type-actionable" href="?type=actionable&status={{ pr_status }}" id="toggle-merged"
+                class="btn {{ 'btn-primary' if pr_type == 'actionable' else 'btn-outline-primary' }} btn-sm">PR I can act on</a>
+          </span>
+      </div>
       <div class="ml-auto">
           <span class="btn-group btn-group-sm issues-tagbar" role="group">
-              <a data-togglebutton="pr-status-open" href="#" id="toggle-open"
-                class="btn btn-primary btn-sm">Open</a>
-              <a data-togglebutton="pr-status-merged" href="#" id="toggle-merged"
-                class="btn btn-outline-primary btn-sm">Merged</a>
-              <a data-togglebutton="pr-status-closed" href="#" id="toggle-closed"
-                class="btn btn-outline-primary btn-sm">Cancelled</a>
-              <a data-togglebutton="pr-status-all" href="#"
-                class="btn btn-outline-primary btn-sm">All</a>
+              <a data-togglebutton="pr-status-open" href="?type={{ pr_type }}&status=open" id="toggle-open"
+                class="btn {{ 'btn-primary' if pr_status == 'open' else 'btn-outline-primary' }} btn-sm">Open</a>
+              <a data-togglebutton="pr-status-merged" href="?type={{ pr_type }}&status=merged" id="toggle-merged"
+                class="btn {{ 'btn-primary' if pr_status == 'merged' else 'btn-outline-primary' }} btn-sm">Merged</a>
+              <a data-togglebutton="pr-status-closed" href="?type={{ pr_type }}&status=cancelled" id="toggle-closed"
+                class="btn {{ 'btn-primary' if pr_status == 'cancelled' else 'btn-outline-primary' }} btn-sm">Cancelled</a>
+              <a data-togglebutton="pr-status-all" href="?type={{ pr_type }}&status=all"
+                class="btn {{ 'btn-primary' if pr_status == 'all' else 'btn-outline-primary' }} btn-sm">All</a>
           </span>
       </div>
     </div>
 
     <div class="d-flex mt-4 mb-2 align-items-center">
+        {% if pr_type == "filed" %}
         <h5 class="font-weight-bold mb-0">Pull Requests Created</h5>
+        {% else %}
+        <h5 class="font-weight-bold mb-0">Pull Requests {{username}} can act on</h5>
+        {% endif %}
         <span class="ml-auto btn btn-outline-secondary border-0 o-100 disabled font-weight-bold">
-          <span id="opened_pr_count"></span> PRs
+          {{ requests_length }} PRs
         </span>
     </div>
 
-    {% for request in requests|selectattr("user.username", "equalto", username) %}
-      {% if request.status|lower != "open" %}
-        {% set hidden = "hidden "%}
-      {% else %}
-        {% set hidden = "" %}
-      {% endif %}
-    {% set htmlclass = hidden+"pr-created pr-status pr-status-"+request.status|lower%}
+    {% for request in requests %}
+    {% set htmlclass = "pr-created pr-status pr-status-"+request.status|lower%}
       {{render_pullrequest_row(request, request.project, username, class=htmlclass, showproject=True)}}
     {% endfor %}
 
+    {% if total_pages > 1 %}
+    {{ pagination_link('page', page, total_pages) }}
+    {% endif %}
 
-    <div class="d-flex mt-4 mb-2 align-items-center">
-        <h5 class="font-weight-bold mb-0">Pull Requests {{username}} can act on</h5>
-        <span class="ml-auto btn btn-outline-secondary border-0 o-100 disabled font-weight-bold">
-          <span id="assigned_pr_count"></span> PRs
-        </span>
-    </div>
-
-    {% for request in requests|rejectattr("user.username", "equalto", username) %}
-      {% if request.status|lower != "open" %}
-        {% set hidden = "hidden "%}
-      {% else %}
-        {% set hidden = "" %}
-      {% endif %}
-    {% set htmlclass = hidden+"pr-assigned pr-status pr-status-"+request.status|lower%}
-      {{render_pullrequest_row(request, request.project, username, class=htmlclass, showproject=True)}}
-    {% endfor %}
   </div>
 </div>
 
@@ -68,32 +64,4 @@
 {{ super() }}
 <script type="text/javascript" nonce="{{ g.nonce }}" src="{{
   url_for('static', filename='tags.js') }}?version={{ g.version}}"></script>
-<script type="text/javascript" nonce="{{ g.nonce }}">
-  count_issues(status='.pr-status-open');
-  $(function(){
-    $('.issues-tagbar .btn').click(function(){
-      var current_btn = $(this).attr("data-togglebutton");
-      count_issues(status='.'+current_btn);
-      $('.issues-tagbar .btn-primary').addClass("btn-outline-primary");
-      $('.issues-tagbar .btn-primary').removeClass("btn-primary");
-      $(this).removeClass("btn-outline-primary");
-      $(this).addClass("btn-primary");
-      if (current_btn == "pr-status-all"){
-        count_issues(status='');
-        $(".pr-status").show();
-      } else {
-        $(".pr-status").hide();
-        $("."+$(this).attr("data-togglebutton")).show();
-      }
-      showNoResultMessage();
-    });
-  });
-
-  function count_issues(status='.pr-status-open') {
-    var assigned_pr_count = $(status + '.pr-assigned').length;
-    var opened_pr_count = $(status + '.pr-created').length;
-    $('#assigned_pr_count').text(assigned_pr_count);
-    $('#opened_pr_count').text(opened_pr_count);
-  }
-</script>
 {% endblock %}
diff --git a/pagure/ui/__init__.py b/pagure/ui/__init__.py
index ee79e27..52317c3 100644
--- a/pagure/ui/__init__.py
+++ b/pagure/ui/__init__.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import flask
 
@@ -17,15 +17,16 @@ UI_NS = flask.Blueprint("ui_ns", __name__)
 # Import the different controllers in the UI namespace/blueprint
 import pagure.config  # noqa: E402
 import pagure.ui.app  # noqa: E402
-from pagure.ui.clone import add_clone_proxy_cmds  # noqa: E402
 import pagure.ui.fork  # noqa: E402
 import pagure.ui.groups  # noqa: E402
+from pagure.ui.clone import add_clone_proxy_cmds  # noqa: E402
 
 if pagure.config.config.get("ENABLE_TICKETS", True):
     import pagure.ui.issues  # noqa: E402
     import pagure.ui.boards  # noqa: E402
-import pagure.ui.plugins  # noqa: E402
-import pagure.ui.repo  # noqa: E402
+
+import pagure.ui.plugins  # noqa: E402, I202
+import pagure.ui.repo  # noqa: E402, I202
 
 if pagure.config.config["PAGURE_AUTH"] == "local":
     import pagure.ui.login  # noqa: E402
@@ -49,7 +50,5 @@ def unauthorized(error):  # pragma: no cover
 @UI_NS.route("/api/")
 @UI_NS.route("/api")
 def api_redirect():
-    """ Redirects the user to the API documentation page.
-
-    """
+    """Redirects the user to the API documentation page."""
     return flask.redirect(flask.url_for("api_ns.api"))
diff --git a/pagure/ui/app.py b/pagure/ui/app.py
index d8ab761..f224085 100644
--- a/pagure/ui/app.py
+++ b/pagure/ui/app.py
@@ -9,7 +9,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import datetime
 import logging
@@ -19,28 +19,27 @@ import flask
 from sqlalchemy.exc import SQLAlchemyError
 
 import pagure.exceptions
+import pagure.forms
 import pagure.lib.git
 import pagure.lib.query
 import pagure.lib.tasks
-import pagure.forms
 import pagure.ui.filters
 from pagure.config import config as pagure_config
 from pagure.flask_app import _get_user, admin_session_timedout
 from pagure.ui import UI_NS
 from pagure.utils import (
     authenticated,
-    is_safe_url,
-    login_required,
     get_task_redirect_url,
+    is_safe_url,
     is_true,
+    login_required,
 )
 
-
 _log = logging.getLogger(__name__)
 
 
 def _filter_acls(repos, acl, user):
-    """ Filter the given list of repositories to return only the ones where
+    """Filter the given list of repositories to return only the ones where
     the user has the specified acl.
     """
     if acl.lower() == "main admin":
@@ -57,8 +56,7 @@ def _filter_acls(repos, acl, user):
 @UI_NS.route("/browse/projects/", endpoint="browse_projects")
 @UI_NS.route("/")
 def index():
-    """ Front page of the application.
-    """
+    """Front page of the application."""
     if (
         authenticated()
         and flask.request.path == "/"
@@ -152,8 +150,7 @@ def get_userdash_common(user):
 @UI_NS.route("/dashboard/projects")
 @login_required
 def userdash_projects():
-    """ User Dashboard page listing projects for the user
-    """
+    """User Dashboard page listing projects for the user"""
     user = _get_user(username=flask.g.fas_user.username)
     userdash_counts, search_data = get_userdash_common(user)
 
@@ -257,8 +254,7 @@ def userdash_projects():
 @UI_NS.route("/dashboard/activity")
 @login_required
 def userdash_activity():
-    """ User Dashboard page listing user activity
-    """
+    """User Dashboard page listing user activity"""
     user = _get_user(username=flask.g.fas_user.username)
     userdash_counts, search_data = get_userdash_common(user)
 
@@ -281,8 +277,7 @@ def userdash_activity():
 @UI_NS.route("/dashboard/groups")
 @login_required
 def userdash_groups():
-    """ User Dashboard page listing a user's groups
-    """
+    """User Dashboard page listing a user's groups"""
     user = _get_user(username=flask.g.fas_user.username)
     userdash_counts, search_data = get_userdash_common(user)
 
@@ -310,8 +305,7 @@ def userdash_groups():
 @UI_NS.route("/dashboard/forks")
 @login_required
 def userdash_forks():
-    """ Forks tab of the user dashboard
-    """
+    """Forks tab of the user dashboard"""
     user = _get_user(username=flask.g.fas_user.username)
     userdash_counts, search_data = get_userdash_common(user)
 
@@ -359,8 +353,7 @@ def userdash_forks():
 @UI_NS.route("/dashboard/watchlist")
 @login_required
 def userdash_watchlist():
-    """ User Dashboard page for a user's watchlist
-    """
+    """User Dashboard page for a user's watchlist"""
 
     watch_list = pagure.lib.query.user_watch_list(
         flask.g.session,
@@ -383,8 +376,7 @@ def userdash_watchlist():
 
 
 def index_auth():
-    """ Front page for authenticated user.
-    """
+    """Front page for authenticated user."""
     user = _get_user(username=flask.g.fas_user.username)
 
     acl = flask.request.args.get("acl", "").strip().lower() or None
@@ -482,8 +474,7 @@ def index_auth():
 @UI_NS.route("/search/")
 @UI_NS.route("/search")
 def search():
-    """ Search this pagure instance for projects or users.
-    """
+    """Search this pagure instance for projects or users."""
     stype = flask.request.args.get("type", "projects")
     term = flask.request.args.get("term")
     page = flask.request.args.get("page", 1)
@@ -517,8 +508,7 @@ def search():
 @UI_NS.route("/users")
 @UI_NS.route("/users/<username>")
 def view_users(username=None):
-    """ Present the list of users.
-    """
+    """Present the list of users."""
     page = flask.request.args.get("page", 1)
     try:
         page = int(page)
@@ -577,8 +567,7 @@ def view_users(username=None):
 @UI_NS.route("/projects/<pattern>")
 @UI_NS.route("/projects/<namespace>/<pattern>")
 def view_projects(pattern=None, namespace=None):
-    """ Present the list of projects.
-    """
+    """Present the list of projects."""
     forks = flask.request.args.get("forks")
     page = flask.request.args.get("page", 1)
 
@@ -678,8 +667,7 @@ def get_userprofile_common(user):
 @UI_NS.route("/user/<username>/")
 @UI_NS.route("/user/<username>")
 def view_user(username):
-    """ Front page of a specific user.
-    """
+    """Front page of a specific user."""
     user = _get_user(username=username)
 
     # public profile, so never show private repos,
@@ -712,8 +700,7 @@ def view_user(username):
 @UI_NS.route("/user/<username>/projects/")
 @UI_NS.route("/user/<username>/projects")
 def userprofile_projects(username):
-    """ Public Profile view of a user's projects.
-    """
+    """Public Profile view of a user's projects."""
     user = _get_user(username=username)
 
     repopage = flask.request.args.get("repopage", 1)
@@ -758,8 +745,7 @@ def userprofile_projects(username):
 @UI_NS.route("/user/<username>/forks/")
 @UI_NS.route("/user/<username>/forks")
 def userprofile_forks(username):
-    """ Public Profile view of a user's forks.
-    """
+    """Public Profile view of a user's forks."""
     user = _get_user(username=username)
 
     forkpage = flask.request.args.get("forkpage", 1)
@@ -804,8 +790,7 @@ def userprofile_forks(username):
 @UI_NS.route("/user2/<username>/")
 @UI_NS.route("/user2/<username>")
 def view_user2(username):
-    """ Front page of a specific user.
-    """
+    """Front page of a specific user."""
     user = _get_user(username=username)
 
     acl = flask.request.args.get("acl", "").strip().lower() or None
@@ -894,13 +879,62 @@ def view_user2(username):
 @UI_NS.route("/user/<username>/requests/")
 @UI_NS.route("/user/<username>/requests")
 def view_user_requests(username):
-    """ Shows the pull-requests for the specified user.
-    """
+    """Shows the pull-requests for the specified user."""
     user = _get_user(username=username)
 
+    pr_type = flask.request.args.get("type", "filed").lower()
+    if pr_type not in ["filed", "actionable"]:
+        flask.flash("Invalid list of PR selected", "error")
+        pr_type = "filed"
+
+    pr_status = flask.request.args.get("status", "open").lower()
+    if pr_status not in ["open", "merged", "cancelled", "all"]:
+        flask.flash("Invalid PR status provided", "error")
+        pr_status = "open"
+
+    page = flask.request.args.get("page", 1)
+    try:
+        page = int(page)
+        if page < 1:
+            page = 1
+    except ValueError:
+        page = 1
+
+    limit = pagure_config["ITEM_PER_PAGE"]
+    start = limit * (page - 1)
+
+    filed = actionable = None
+    if pr_type == "filed":
+        filed = user.user
+    else:
+        actionable = user.user
+
+    status = None
+    if pr_status == "open":
+        status = "Open"
+    elif pr_status == "merged":
+        status = "Merged"
+    elif pr_status == "cancelled":
+        status = "Closed"
+
     requests = pagure.lib.query.get_pull_request_of_user(
-        flask.g.session, username=username
+        flask.g.session,
+        username=username,
+        filed=filed,
+        actionable=actionable,
+        status=status,
+        offset=start,
+        limit=limit,
+    )
+    requests_length = pagure.lib.query.get_pull_request_of_user(
+        flask.g.session,
+        username=username,
+        filed=filed,
+        actionable=actionable,
+        status=status,
+        count=True,
     )
+    total_pages = int(ceil(requests_length / float(limit)))
 
     userprofile_common = get_userprofile_common(user)
 
@@ -909,7 +943,12 @@ def view_user_requests(username):
         username=username,
         user=user,
         requests=requests,
+        requests_length=requests_length,
         select="requests",
+        pr_type=pr_type,
+        pr_status=pr_status,
+        page=page,
+        total_pages=total_pages,
         repos_length=userprofile_common["repos_length"],
         forks_length=userprofile_common["forks_length"],
     )
@@ -1000,8 +1039,7 @@ def userprofile_groups(username):
 @UI_NS.route("/new", methods=("GET", "POST"))
 @login_required
 def new_project():
-    """ Form to create a new project.
-    """
+    """Form to create a new project."""
 
     user = pagure.lib.query.search_user(
         flask.g.session, username=flask.g.fas_user.username
@@ -1089,14 +1127,14 @@ def new_project():
 
 @UI_NS.route("/wait/<taskid>")
 def wait_task(taskid):
-    """ Shows a wait page until the task finishes. """
+    """Shows a wait page until the task finishes."""
     task = pagure.lib.tasks.get_result(taskid)
 
     is_js = is_true(flask.request.args.get("js"))
 
     prev = flask.request.args.get("prev")
     if not is_safe_url(prev):
-        prev = flask.url_for("index")
+        prev = flask.url_for("ui_ns.index")
 
     count = flask.request.args.get("count", 0)
     try:
@@ -1123,8 +1161,7 @@ def wait_task(taskid):
 @UI_NS.route("/settings")
 @login_required
 def user_settings():
-    """ Update the user settings.
-    """
+    """Update the user settings."""
     if admin_session_timedout():
         return flask.redirect(
             flask.url_for("auth_login", next=flask.request.url)
@@ -1138,8 +1175,7 @@ def user_settings():
 @UI_NS.route("/settings/usersettings", methods=["POST"])
 @login_required
 def update_user_settings():
-    """ Update the user's settings set in the settings page.
-    """
+    """Update the user's settings set in the settings page."""
     if admin_session_timedout():
         if flask.request.method == "POST":
             flask.flash("Action canceled, try it again", "error")
@@ -1177,8 +1213,7 @@ def update_user_settings():
 @UI_NS.route("/settings/usersettings/addkey", methods=["POST"])
 @login_required
 def add_user_sshkey():
-    """ Add the specified SSH key to the user.
-    """
+    """Add the specified SSH key to the user."""
     if admin_session_timedout():
         if flask.request.method == "POST":
             flask.flash("Action canceled, try it again", "error")
@@ -1234,8 +1269,7 @@ def add_user_sshkey():
 @UI_NS.route("/settings/usersettings/removekey/<int:keyid>", methods=["POST"])
 @login_required
 def remove_user_sshkey(keyid):
-    """ Removes an SSH key from the user.
-    """
+    """Removes an SSH key from the user."""
     if admin_session_timedout():
         if flask.request.method == "POST":
             flask.flash("Action canceled, try it again", "error")
@@ -1287,7 +1321,7 @@ def remove_user_sshkey(keyid):
 
 @UI_NS.route("/markdown/", methods=["POST"])
 def markdown_preview():
-    """ Return the provided markdown text in html.
+    """Return the provided markdown text in html.
 
     The text has to be provided via the parameter 'content' of a POST query.
     """
@@ -1301,8 +1335,7 @@ def markdown_preview():
 @UI_NS.route("/settings/email/drop", methods=["POST"])
 @login_required
 def remove_user_email():
-    """ Remove the specified email from the logged in user.
-    """
+    """Remove the specified email from the logged in user."""
     if admin_session_timedout():
         return flask.redirect(
             flask.url_for("auth_login", next=flask.request.url)
@@ -1346,8 +1379,7 @@ def remove_user_email():
 @UI_NS.route("/settings/email/add", methods=["GET", "POST"])
 @login_required
 def add_user_email():
-    """ Add a new email for the logged in user.
-    """
+    """Add a new email for the logged in user."""
     if admin_session_timedout():
         return flask.redirect(
             flask.url_for("auth_login", next=flask.request.url)
@@ -1381,8 +1413,7 @@ def add_user_email():
 @UI_NS.route("/settings/email/default", methods=["POST"])
 @login_required
 def set_default_email():
-    """ Set the default email address of the user.
-    """
+    """Set the default email address of the user."""
     if admin_session_timedout():
         return flask.redirect(
             flask.url_for("auth_login", next=flask.request.url)
@@ -1419,8 +1450,7 @@ def set_default_email():
 @UI_NS.route("/settings/email/resend", methods=["POST"])
 @login_required
 def reconfirm_email():
-    """ Re-send the email address of the user.
-    """
+    """Re-send the email address of the user."""
     if admin_session_timedout():
         return flask.redirect(
             flask.url_for("auth_login", next=flask.request.url)
@@ -1449,8 +1479,7 @@ def reconfirm_email():
 @UI_NS.route("/settings/email/confirm/<token>/")
 @UI_NS.route("/settings/email/confirm/<token>")
 def confirm_email(token):
-    """ Confirm a new email.
-    """
+    """Confirm a new email."""
     if admin_session_timedout():
         return flask.redirect(
             flask.url_for("auth_login", next=flask.request.url)
@@ -1485,7 +1514,7 @@ def confirm_email(token):
 @UI_NS.route("/ssh_info/")
 @UI_NS.route("/ssh_info")
 def ssh_hostkey():
-    """ Endpoint returning information about the SSH hostkey and fingerprint
+    """Endpoint returning information about the SSH hostkey and fingerprint
     of the current pagure instance.
     """
     return flask.render_template("doc_ssh_keys.html")
@@ -1495,8 +1524,7 @@ def ssh_hostkey():
 @UI_NS.route("/settings/token/new", methods=("GET", "POST"))
 @login_required
 def add_api_user_token():
-    """ Create an user token (not project specific).
-    """
+    """Create an user token (not project specific)."""
     if admin_session_timedout():
         if flask.request.method == "POST":
             flask.flash("Action canceled, try it again", "error")
@@ -1514,10 +1542,14 @@ def add_api_user_token():
 
     if form.validate_on_submit():
         try:
+            description = None
+            # Check if the optional value is filled
+            if form.description.data:
+                description = form.description.data.strip()
             pagure.lib.query.add_token_to_user(
                 flask.g.session,
                 project=None,
-                description=form.description.data.strip() or None,
+                description=description,
                 acls=form.acls.data,
                 username=user.username,
                 expiration_date=form.expiration_date.data,
@@ -1547,8 +1579,7 @@ def add_api_user_token():
 @UI_NS.route("/settings/token/revoke/<token_id>", methods=["POST"])
 @login_required
 def revoke_api_user_token(token_id):
-    """ Revoke a user token (ie: not project specific).
-    """
+    """Revoke a user token (ie: not project specific)."""
     if admin_session_timedout():
         flask.flash("Action canceled, try it again", "error")
         url = flask.url_for(".user_settings")
@@ -1584,8 +1615,7 @@ def revoke_api_user_token(token_id):
 @UI_NS.route("/settings/token/renew/<token_id>", methods=["POST"])
 @login_required
 def renew_api_user_token(token_id):
-    """ Renew a user token (ie: not project specific).
-    """
+    """Renew a user token (ie: not project specific)."""
     if admin_session_timedout():
         flask.flash("Action canceled, try it again", "error")
         url = flask.url_for(".user_settings")
@@ -1629,12 +1659,11 @@ def renew_api_user_token(token_id):
 @UI_NS.route("/settings/forcelogout", methods=("POST",))
 @login_required
 def force_logout():
-    """ Set refuse_sessions_before, logging the user out everywhere
-    """
+    """Set refuse_sessions_before, logging the user out everywhere"""
     if admin_session_timedout():
         flask.flash("Action canceled, try it again", "error")
         return flask.redirect(
-            flask.url_for("auth_login", next=flask.request.url)
+            flask.url_for("auth_login", next=flask.request.url, _external=True)
         )
 
     # we just need an empty form here to validate that csrf token is present
@@ -1646,13 +1675,13 @@ def force_logout():
         user.refuse_sessions_before = datetime.datetime.utcnow()
         flask.g.session.commit()
         flask.flash("All active sessions logged out")
-    return flask.redirect(flask.url_for("ui_ns.user_settings"))
+    return flask.redirect(flask.url_for("ui_ns.user_settings", _external=True))
 
 
 @UI_NS.route("/about")
 @UI_NS.route("/about/")
 def help():
-    """ A page to direct users to the appropriate places to get assistance,
-        or find basic instance information.
+    """A page to direct users to the appropriate places to get assistance,
+    or find basic instance information.
     """
     return flask.render_template("about.html")
diff --git a/pagure/ui/boards.py b/pagure/ui/boards.py
index 9f11fae..133159b 100644
--- a/pagure/ui/boards.py
+++ b/pagure/ui/boards.py
@@ -14,23 +14,19 @@
 # pylint: disable=too-many-statements
 
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import logging
 
 import flask
 
-from pagure.ui import UI_NS
-from pagure.utils import (
-    authenticated,
-    login_required,
-)
 from pagure.decorators import (
-    is_repo_admin,
-    is_admin_sess_timedout,
     has_issue_tracker,
+    is_admin_sess_timedout,
+    is_repo_admin,
 )
-
+from pagure.ui import UI_NS
+from pagure.utils import authenticated, login_required
 
 _log = logging.getLogger(__name__)
 
@@ -45,8 +41,7 @@ _log = logging.getLogger(__name__)
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/boards/<board_name>")
 @has_issue_tracker
 def view_board(repo, board_name, username=None, namespace=None):
-    """ View a board
-    """
+    """View a board"""
 
     project = flask.g.repo
 
@@ -113,8 +108,7 @@ def view_board(repo, board_name, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def view_board_settings(repo, board_name, username=None, namespace=None):
-    """ Presents and update the settings of the board
-    """
+    """Presents and update the settings of the board"""
     project = flask.g.repo
 
     if not project.boards:
diff --git a/pagure/ui/clone.py b/pagure/ui/clone.py
index dd769b5..59a225e 100644
--- a/pagure/ui/clone.py
+++ b/pagure/ui/clone.py
@@ -8,25 +8,25 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import base64
 import logging
+import os
 import subprocess
 import tempfile
-import os
 
 import flask
 import requests
 import werkzeug.wsgi
 
 import pagure.exceptions
+import pagure.forms
 import pagure.lib.git
 import pagure.lib.mimetype
 import pagure.lib.plugins
 import pagure.lib.query
 import pagure.lib.tasks
-import pagure.forms
 import pagure.ui.plugins
 from pagure.config import config as pagure_config
 from pagure.ui import UI_NS
@@ -36,7 +36,7 @@ _auth_log = logging.getLogger("pagure_auth")
 
 
 def _get_remote_user(project):
-    """ Returns the remote user using either the content of
+    """Returns the remote user using either the content of
     ``flask.g.remote_user`` or checking the headers for ``Authorization``
     and check if the provided API token is valid.
     """
@@ -94,7 +94,7 @@ def _get_remote_user(project):
 
 
 def proxy_raw_git(project):
-    """ Proxy a request to Git or gitolite3 via a subprocess.
+    """Proxy a request to Git or gitolite3 via a subprocess.
 
     This should get called after it is determined the requested project
     is not on repoSpanner.
@@ -227,7 +227,7 @@ def proxy_raw_git(project):
 
 
 def proxy_repospanner(project, service):
-    """ Proxy a request to repoSpanner.
+    """Proxy a request to repoSpanner.
 
     Args:
         project (model.Project): The project being accessed
@@ -284,7 +284,7 @@ def proxy_repospanner(project, service):
 
 
 def clone_proxy(project, username=None, namespace=None):
-    """ Proxy the /info/refs endpoint for HTTP pull/push.
+    """Proxy the /info/refs endpoint for HTTP pull/push.
 
     Note that for the clone endpoints, it's very explicit that <repo> has been
     renamed to <project>, to avoid the automatic repo searching from flask_app.
@@ -311,7 +311,7 @@ def clone_proxy(project, username=None, namespace=None):
     p1 = pagure.lib.query.get_authorized_project(
         flask.g.session, project, user=username, namespace=namespace
     )
-    p1_path = "invalid repo"
+    p1_path = "invalid repo: %s/%s/%s" % (username, namespace, project)
     if p1:
         p1_path = p1.path
     remote_user = _get_remote_user(p1)
@@ -426,7 +426,7 @@ def clone_proxy(project, username=None, namespace=None):
 
 
 def add_clone_proxy_cmds():
-    """ This function adds flask routes for all possible clone paths.
+    """This function adds flask routes for all possible clone paths.
 
     This comes down to:
     /(fork/<username>/)(<namespace>/)<project>(.git)
diff --git a/pagure/ui/fas_login.py b/pagure/ui/fas_login.py
index 1b29303..f565d8c 100644
--- a/pagure/ui/fas_login.py
+++ b/pagure/ui/fas_login.py
@@ -1,27 +1,27 @@
 # -*- coding: utf-8 -*-
 
 """
- (c) 2014-2017 - Copyright Red Hat Inc
+ (c) 2014-2020 - Copyright Red Hat Inc
 
  Authors:
    Pierre-Yves Chibon <pingou@pingoured.fr>
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import logging
+from base64 import b64decode
 
 import flask
+import flask_fas_openid
 from flask import Markup
-
 from sqlalchemy.exc import SQLAlchemyError
 
 import pagure.lib.query
 import pagure.utils
-from pagure.flask_app import logout
 from pagure.config import config as pagure_config
-import flask_fas_openid
+from pagure.flask_app import logout
 
 FAS = flask_fas_openid.FAS()
 
@@ -30,7 +30,7 @@ _log = logging.getLogger(__name__)
 
 @FAS.postlogin
 def set_user(return_url):
-    """ After login method. """
+    """After login method."""
     if flask.g.fas_user.username is None:
         flask.flash(
             "It looks like your OpenID provider did not provide an "
@@ -48,6 +48,15 @@ def set_user(return_url):
     if not user:
         flask.session["_new_user"] = True
     else:
+        if not flask.g.fas_user.email:
+            flask.flash(
+                "No email address was returned by your OpenID provider, this "
+                "information is mandatory for pagure",
+                "error",
+            )
+            logout()
+            return flask.redirect(return_url)
+
         user_email = pagure.lib.query.search_user(
             flask.g.session, email=flask.g.fas_user.email
         )
@@ -62,12 +71,20 @@ def set_user(return_url):
 
     try:
         try:
+
+            ssh_key = flask.g.fas_user.get("ssh_key")
+            if ssh_key is not None:
+                try:
+                    ssh_key = b64decode(ssh_key).decode("ascii")
+                except (TypeError, ValueError):
+                    pass
+
             pagure.lib.query.set_up_user(
                 session=flask.g.session,
                 username=flask.g.fas_user.username,
                 fullname=flask.g.fas_user.fullname,
                 default_email=flask.g.fas_user.email,
-                ssh_key=flask.g.fas_user.get("ssh_key"),
+                ssh_key=ssh_key,
                 keydir=pagure_config.get("GITOLITE_KEYDIR", None),
             )
         except pagure.exceptions.PagureException as err:
@@ -134,4 +151,9 @@ def set_user(return_url):
     except pagure.exceptions.PagureException as err:
         flask.flash(str(err), "error")
 
+    if flask.g.fas_user.get("ssh_key"):
+        del flask.g.fas_user.ssh_key
+    if flask.session.get("FLASK_FAS_OPENID_USER").get("ssh_key"):
+        del flask.session["FLASK_FAS_OPENID_USER"]["ssh_key"]
+
     return flask.redirect(return_url)
diff --git a/pagure/ui/filters.py b/pagure/ui/filters.py
index 73799ed..40cc276 100644
--- a/pagure/ui/filters.py
+++ b/pagure/ui/filters.py
@@ -13,37 +13,39 @@
 # pylint: disable=too-many-locals
 
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
-import textwrap
 import logging
 import os
+import textwrap
 from os.path import splitext
 
 import arrow
 import bleach
 import flask
-import six
 import pygit2
+import six
 
-from six.moves.urllib.parse import urlparse, parse_qsl
-from jinja2 import escape
+try:
+    from jinja2 import escape
+except ImportError:
+    from markupsafe import escape
+from six.moves.urllib.parse import parse_qsl, urlparse
 
 import pagure.exceptions
-import pagure.lib.query
 import pagure.forms
+import pagure.lib.query
 from pagure.config import config as pagure_config
 from pagure.ui import UI_NS
 from pagure.utils import authenticated, is_repo_committer, is_true
 
-
 _log = logging.getLogger(__name__)
 # Jinja filters
 
 
 @UI_NS.app_template_filter("hasattr")
 def jinja_hasattr(obj, string):
-    """ Template filter checking if the provided object at the provided
+    """Template filter checking if the provided object at the provided
     string as attribute
     """
     return hasattr(obj, string)
@@ -51,15 +53,13 @@ def jinja_hasattr(obj, string):
 
 @UI_NS.app_template_filter("render")
 def jinja_render(tmpl, **kwargs):
-    """ Render the given template with the provided arguments
-    """
+    """Render the given template with the provided arguments"""
     return flask.render_template_string(tmpl, **kwargs)
 
 
 @UI_NS.app_template_filter("humanize")
 def humanize_date(date):
-    """ Template filter returning the last commit date of the provided repo.
-    """
+    """Template filter returning the last commit date of the provided repo."""
     if date:
         return arrow.get(date).humanize()
 
@@ -67,7 +67,7 @@ def humanize_date(date):
 @UI_NS.app_template_filter("format_ts")
 @UI_NS.app_template_filter("format_datetime")
 def format_ts(string):
-    """ Template filter transforming a timestamp, datetime or anything
+    """Template filter transforming a timestamp, datetime or anything
     else arrow.get() can handle to a human-readable date
     """
     # We *could* enhance this by allowing users to specify preferred
@@ -76,14 +76,32 @@ def format_ts(string):
     # always use UTC timezone, and we don't use localized forms like
     # %b or %d because they will be 'localized' for the *server*.
     # This format should be pretty 'locale-neutral'.
+
+    # Return empty string if timestamp is None
+    # This will prevent any formatting error in arrow
+    if string is None:
+        return ""
     arr = arrow.get(string)
     return arr.strftime("%Y-%m-%d %H:%M:%S %Z")
 
 
+@UI_NS.app_template_filter("humanize_tooltip")
+def humanize_with_tooltip(date):
+    """Template filter returning a human readable time string with an
+    UTC timestamp tooltip
+    """
+    if date:
+        humanized = humanize_date(date)
+        utc = format_ts(date)
+        output = '<span title="{utc}" data-toggle="tooltip">{humanized}</span>'
+        output = output.format(utc=utc, humanized=humanized)
+        return output
+    return ""
+
+
 @UI_NS.app_template_filter("linkify")
 def linkify_text(text):
-    """ escape all html tags with bleach, then use bleach to linkify
-    """
+    """escape all html tags with bleach, then use bleach to linkify"""
     if text:
         cleaned = bleach.clean(text, tags=[], attributes=[])
         return bleach.linkify(cleaned)
@@ -93,9 +111,9 @@ def linkify_text(text):
 
 @UI_NS.app_template_filter("syntax_alias")
 def get_syntax_alias(filename):
-    """ return an alias based on the filename that is used to
-        override the automatic syntax highlighting dectection
-        by highlight.js
+    """return an alias based on the filename that is used to
+    override the automatic syntax highlighting dectection
+    by highlight.js
     """
 
     override_rules = pagure_config.get(
@@ -123,8 +141,7 @@ def format_loc(
     index=None,
     isprdiff=False,
 ):
-    """ Template filter putting the provided lines of code into a table
-    """
+    """Template filter putting the provided lines of code into a table"""
     if loc is None:
         return
 
@@ -149,38 +166,33 @@ def format_loc(
     for key in comments:
         comments[key] = sorted(comments[key], key=lambda obj: obj.date_created)
 
-    if not index:
-        index = ""
+    if isinstance(filename, str) and six.PY2:
+        filename = filename.decode("UTF-8")
 
     cnt = 1
     for line in loc.split("\n"):
-        if filename and commit:
-            if isinstance(filename, str) and six.PY2:
-                filename = filename.decode("UTF-8")
-
-            if isprdiff and (
-                line.startswith("@@")
-                or line.startswith("+")
-                or line.startswith("-")
-            ):
-                if line.startswith("@@"):
-                    output.append(
-                        '<tr class="stretch-table-column bg-light"\
-                    id="c-%(commit)s-%(cnt_lbl)s">'
-                        % ({"cnt_lbl": cnt, "commit": commit})
-                    )
-                elif line.startswith("+"):
-                    output.append(
-                        '<tr class="stretch-table-column alert-success" \
-                    id="c-%(commit)s-%(cnt_lbl)s">'
-                        % ({"cnt_lbl": cnt, "commit": commit})
-                    )
-                elif line.startswith("-"):
-                    output.append(
-                        '<tr class="stretch-table-column alert-danger" \
-                    id="c-%(commit)s-%(cnt_lbl)s">'
-                        % ({"cnt_lbl": cnt, "commit": commit})
-                    )
+        if line.startswith("@@"):
+            output.append(
+                '<tr class="stretch-table-column bg-light"\
+            id="c-%(commit)s-%(cnt_lbl)s">'
+                % ({"cnt_lbl": cnt, "commit": commit})
+            )
+            output.append(
+                '<td class="cell1"></td><td class="prc border-right"></td>'
+            )
+        else:
+            if line.startswith("+"):
+                output.append(
+                    '<tr class="stretch-table-column alert-success" \
+                id="c-%(commit)s-%(cnt_lbl)s">'
+                    % ({"cnt_lbl": cnt, "commit": commit})
+                )
+            elif line.startswith("-"):
+                output.append(
+                    '<tr class="stretch-table-column alert-danger" \
+                id="c-%(commit)s-%(cnt_lbl)s">'
+                    % ({"cnt_lbl": cnt, "commit": commit})
+                )
             else:
                 output.append(
                     '<tr id="c-%(commit)s-%(cnt_lbl)s">'
@@ -211,13 +223,6 @@ def format_loc(
                     }
                 )
             )
-        else:
-            output.append(
-                '<tr><td class="cell1">'
-                '<a id="%(cnt)s" href="#%(cnt)s" data-line-number='
-                '"%(cnt_lbl)s"></a></td>'
-                % ({"cnt": "%s_%s" % (index, cnt), "cnt_lbl": cnt})
-            )
 
         cnt += 1
         if not line:
@@ -254,29 +259,24 @@ def format_loc(
                         + 'title="Open changed file"></span></a>'
                     )
 
-        if isprdiff and (
-            line.startswith("@@")
-            or line.startswith("+")
-            or line.startswith("-")
-        ):
-            if line.startswith("@@"):
-                output.append(
-                    '<td class="cell2 stretch-table-column">\
-                    <pre class="text-muted"><code>%s</code></pre></td>'
-                    % line
-                )
-            elif line.startswith("+"):
-                output.append(
-                    '<td class="cell2 stretch-table-column">\
-                    <pre class="alert-success"><code>%s</code></pre></td>'
-                    % escape(line)
-                )
-            elif line.startswith("-"):
-                output.append(
-                    '<td class="cell2 stretch-table-column">\
-                    <pre class="alert-danger"><code>%s</code></pre></td>'
-                    % escape(line)
-                )
+        if line.startswith("@@"):
+            output.append(
+                '<td class="cell2 stretch-table-column">\
+                <pre class="text-muted"><code>%s</code></pre></td>'
+                % line
+            )
+        elif line.startswith("+"):
+            output.append(
+                '<td class="cell2 stretch-table-column">\
+                <pre class="alert-success"><code>%s</code></pre></td>'
+                % escape(line)
+            )
+        elif line.startswith("-"):
+            output.append(
+                '<td class="cell2 stretch-table-column">\
+                <pre class="alert-danger"><code>%s</code></pre></td>'
+                % escape(line)
+            )
         else:
             output.append(
                 '<td class="cell2"><pre><code>%s</code></pre></td>'
@@ -411,7 +411,7 @@ def format_loc(
 
 @UI_NS.app_template_filter("blame_loc")
 def blame_loc(loc, repo, username, blame):
-    """ Template filter putting the provided lines of code into a table
+    """Template filter putting the provided lines of code into a table
 
 
     This method blame lines of code (loc) takes as input a text (lines of
@@ -491,8 +491,7 @@ def blame_loc(loc, repo, username, blame):
 
 @UI_NS.app_template_filter("wraps")
 def text_wraps(text, size=10):
-    """ Template filter to wrap text at a specified size
-    """
+    """Template filter to wrap text at a specified size"""
     if text:
         parts = textwrap.wrap(text, size)
         if len(parts) > 1:
@@ -503,9 +502,8 @@ def text_wraps(text, size=10):
 
 
 @UI_NS.app_template_filter("avatar")
-def avatar(packager, size=64, css_class=None):
-    """ Template filter that returns html for avatar of any given Username.
-    """
+def avatar(packager, size=64, css_class=None, src_tag="src"):
+    """Template filter that returns html for avatar of any given Username."""
     if not isinstance(packager, six.text_type):
         packager = packager.decode("utf-8")
 
@@ -518,8 +516,9 @@ def avatar(packager, size=64, css_class=None):
     if css_class:
         class_string = class_string + " " + css_class
 
-    output = '<img class="%s" src="%s"/>' % (
+    output = '<img class="%s lazyload" %s="%s"/>' % (
         class_string,
+        src_tag,
         avatar_url(packager, size),
     )
 
@@ -528,8 +527,7 @@ def avatar(packager, size=64, css_class=None):
 
 @UI_NS.app_template_filter("avatar_url")
 def avatar_url(email, size=64):
-    """ Template filter that returns html for avatar of any given Email.
-    """
+    """Template filter that returns html for avatar of any given Email."""
     return pagure.lib.query.avatar_url_from_email(email, size)
 
 
@@ -541,7 +539,7 @@ def shorted_commit(cid):
 
 @UI_NS.app_template_filter("markdown")
 def markdown_filter(text):
-    """ Template filter converting a string into html content using the
+    """Template filter converting a string into html content using the
     markdown library.
     """
     return pagure.lib.query.text2markdown(text)
@@ -576,7 +574,7 @@ def patch_to_diff(patch):
 
 @UI_NS.app_template_filter("author2user")
 def author_to_user(author, size=16, cssclass=None, with_name=True):
-    """ Template filter transforming a pygit2 Author object into a text
+    """Template filter transforming a pygit2 Author object into a text
     either with just the username or linking to the user in pagure.
     """
     output = escape(author.name)
@@ -611,8 +609,7 @@ def author_to_user(author, size=16, cssclass=None, with_name=True):
 
 @UI_NS.app_template_filter("author2avatar")
 def author_to_avatar(author, size=32):
-    """ Template filter transforming a pygit2 Author object into an avatar.
-    """
+    """Template filter transforming a pygit2 Author object into an avatar."""
     if not author.email:
         return ""
     user = pagure.lib.query.search_user(flask.g.session, email=author.email)
@@ -622,7 +619,7 @@ def author_to_avatar(author, size=32):
 
 @UI_NS.app_template_filter("author2user_commits")
 def author_to_user_commits(author, link, size=16, cssclass=None):
-    """ Template filter transforming a pygit2 Author object into a text
+    """Template filter transforming a pygit2 Author object into a text
     either with just the username or linking to the user in pagure.
     """
     output = author.name
@@ -643,7 +640,7 @@ def author_to_user_commits(author, link, size=16, cssclass=None):
 
 @UI_NS.app_template_filter("InsertDiv")
 def insert_div(content):
-    """ Template filter inserting an opening <div> and closing </div>
+    """Template filter inserting an opening <div> and closing </div>
     after the first title and then at the end of the content.
     """
     # This is quite a hack but simpler solution using .replace() didn't work
@@ -669,7 +666,7 @@ def insert_div(content):
 
 @UI_NS.app_template_filter("noJS")
 def no_js(content, ignore=None):
-    """ Template filter replacing <script by &lt;script and </script> by
+    """Template filter replacing <script by &lt;script and </script> by
     &lt;/script&gt;
     """
     return pagure.lib.query.clean_input(content, ignore=ignore)
@@ -677,8 +674,7 @@ def no_js(content, ignore=None):
 
 @UI_NS.app_template_filter("toRGB")
 def int_to_rgb(percent):
-    """ Template filter converting a given percentage to a css RGB value.
-    """
+    """Template filter converting a given percentage to a css RGB value."""
     output = "rgb(255, 0, 0);"
     try:
         percent = int(percent)
@@ -696,8 +692,7 @@ def int_to_rgb(percent):
 
 @UI_NS.app_template_filter("increment_largest_priority")
 def largest_priority(dictionary):
-    """ Template filter to return the largest priority +1
-    """
+    """Template filter to return the largest priority +1"""
     if dictionary:
         keys = [int(k) for k in dictionary if k]
         if keys:
@@ -707,7 +702,7 @@ def largest_priority(dictionary):
 
 @UI_NS.app_template_filter("unicode")
 def convert_unicode(text):
-    """ If the provided string is a binary string, this filter converts it
+    """If the provided string is a binary string, this filter converts it
     to UTF-8 (unicode).
     """
     if isinstance(text, str) and six.PY2:
@@ -718,7 +713,7 @@ def convert_unicode(text):
 
 @UI_NS.app_template_filter("combine_url")
 def combine_url(url, page, pagetitle, **kwargs):
-    """ Add the specified arguments in the provided kwargs dictionary to
+    """Add the specified arguments in the provided kwargs dictionary to
     the given URL.
     """
     url_obj = urlparse(url)
@@ -746,7 +741,7 @@ def combine_url(url, page, pagetitle, **kwargs):
 
 @UI_NS.app_template_filter("add_or_remove")
 def add_or_remove(item, items):
-    """ Adds the item to the list if it is not in there and remove it
+    """Adds the item to the list if it is not in there and remove it
     otherwise.
     """
     if item in items:
@@ -758,8 +753,7 @@ def add_or_remove(item, items):
 
 @UI_NS.app_template_filter("table_sort_arrow")
 def table_sort_arrow(column, order_key, order):
-    """ Outputs an arrow icon if the column is currently being sorted on
-    """
+    """Outputs an arrow icon if the column is currently being sorted on"""
     arrow_html = '<span class="oi" data-glyph="arrow-thick-{0}"></span>'
     if column == order_key:
         if order == "desc":
@@ -771,8 +765,7 @@ def table_sort_arrow(column, order_key, order):
 
 @UI_NS.app_template_filter("table_get_link_order")
 def table_get_link_order(column, order_key, order):
-    """ Get the correct order parameter value for the table heading link
-    """
+    """Get the correct order parameter value for the table heading link"""
     if column == order_key:
         # If the user is clicking on the column again, they want the
         # oposite order
@@ -787,8 +780,7 @@ def table_get_link_order(column, order_key, order):
 
 @UI_NS.app_template_filter("flag2label")
 def flag_to_label(flag):
-    """ For a given flag return the bootstrap label to use
-    """
+    """For a given flag return the bootstrap label to use"""
     return pagure_config["FLAG_STATUSES_LABELS"][flag.status.lower()]
 
 
@@ -822,7 +814,7 @@ def user_can_clone_ssh(username):
 
 @UI_NS.app_template_filter("user_group_can_ssh_commit")
 def user_group_can_ssh_commit(username):
-    """ Returns whether the user is in a group that has ssh access. """
+    """Returns whether the user is in a group that has ssh access."""
     ssh_access_groups = pagure_config.get("SSH_ACCESS_GROUPS") or []
     if not ssh_access_groups:
         # ssh access is not restricted to one or more groups
@@ -840,7 +832,7 @@ def user_group_can_ssh_commit(username):
 
 @UI_NS.app_template_filter("git_url_ssh")
 def get_git_url_ssh(complement=""):
-    """ Return the GIT SSH URL to be displayed in the UI based on the
+    """Return the GIT SSH URL to be displayed in the UI based on the
     content of the configuration file.
     """
     git_url_ssh = pagure_config.get("GIT_URL_SSH")
@@ -856,7 +848,7 @@ def get_git_url_ssh(complement=""):
 
 @UI_NS.app_template_filter("patch_stats")
 def get_patch_stats(patch):
-    """ Return a dict of stats about the provided patch."""
+    """Return a dict of stats about the provided patch."""
     try:
         output = pagure.lib.git.get_stats_patch(patch)
     except pagure.exceptions.PagureException:
@@ -867,7 +859,7 @@ def get_patch_stats(patch):
 
 @UI_NS.app_template_filter("get_default_branch")
 def get_default_branch(repo):
-    """ Given a pygit2.Repository object, extracts the default branch. """
+    """Given a pygit2.Repository object, extracts the default branch."""
     default_branch = None
 
     try:
diff --git a/pagure/ui/fork.py b/pagure/ui/fork.py
index 46388f7..53c707f 100644
--- a/pagure/ui/fork.py
+++ b/pagure/ui/fork.py
@@ -16,8 +16,9 @@
 # pylint: disable=too-many-lines
 
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
+import json
 import logging
 import os
 from math import ceil
@@ -29,26 +30,25 @@ from sqlalchemy.exc import SQLAlchemyError
 import pagure
 import pagure.doc_utils
 import pagure.exceptions
+import pagure.forms
 import pagure.lib.git
 import pagure.lib.plugins
 import pagure.lib.query
 import pagure.lib.tasks
-import pagure.forms
 from pagure.config import config as pagure_config
 from pagure.ui import UI_NS
 from pagure.utils import (
-    login_required,
     __get_file_in_tree,
     get_parent_repo_path,
     is_true,
+    login_required,
 )
 
-
 _log = logging.getLogger(__name__)
 
 
 def _get_parent_request_repo_path(repo):
-    """ Return the path of the parent git repository corresponding to the
+    """Return the path of the parent git repository corresponding to the
     provided Repository object from the DB.
     """
     if repo.parent:
@@ -66,8 +66,7 @@ def _get_parent_request_repo_path(repo):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/pull-requests/")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/pull-requests")
 def request_pulls(repo, username=None, namespace=None):
-    """ List all Pull-requests associated to a repo
-    """
+    """List all Pull-requests associated to a repo"""
     status = flask.request.args.get("status", "Open")
     tags = flask.request.args.getlist("tags")
     tags = [tag.strip() for tag in tags if tag.strip()]
@@ -242,8 +241,7 @@ def request_pulls(repo, username=None, namespace=None):
     "/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>"
 )
 def request_pull(repo, requestid, username=None, namespace=None):
-    """ View a pull request with the changes from the fork into the project.
-    """
+    """View a pull request with the changes from the fork into the project."""
     repo = flask.g.repo
 
     _log.info("Viewing pull Request #%s repo: %s", requestid, repo.fullname)
@@ -321,6 +319,10 @@ def request_pull(repo, requestid, username=None, namespace=None):
     if diff:
         diff.find_similar()
 
+    warning_characters = pagure.lib.query.find_warning_characters(
+        repo_obj, diff_commits
+    )
+
     form = pagure.forms.MergePRForm()
     trigger_ci_pr_form = pagure.forms.TriggerCIPRForm()
 
@@ -369,6 +371,8 @@ def request_pull(repo, requestid, username=None, namespace=None):
         can_delete_branch=can_delete_branch,
         trigger_ci=trigger_ci,
         trigger_ci_pr_form=trigger_ci_pr_form,
+        flag_statuses_labels=json.dumps(pagure_config["FLAG_STATUSES_LABELS"]),
+        warning_characters=warning_characters,
     )
 
 
@@ -379,8 +383,7 @@ def request_pull(repo, requestid, username=None, namespace=None):
     "/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>.patch"
 )
 def request_pull_patch(repo, requestid, username=None, namespace=None):
-    """ Returns the commits from the specified pull-request as patches.
-    """
+    """Returns the commits from the specified pull-request as patches."""
     return request_pull_to_diff_or_patch(
         repo, requestid, username, namespace, diff=False
     )
@@ -393,8 +396,7 @@ def request_pull_patch(repo, requestid, username=None, namespace=None):
     "/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>.diff"
 )
 def request_pull_diff(repo, requestid, username=None, namespace=None):
-    """ Returns the commits from the specified pull-request as patches.
-    """
+    """Returns the commits from the specified pull-request as patches."""
     return request_pull_to_diff_or_patch(
         repo, requestid, username, namespace, diff=True
     )
@@ -403,7 +405,7 @@ def request_pull_diff(repo, requestid, username=None, namespace=None):
 def request_pull_to_diff_or_patch(
     repo, requestid, username=None, namespace=None, diff=False
 ):
-    """ Returns the commits from the specified pull-request as patches.
+    """Returns the commits from the specified pull-request as patches.
 
     :arg repo: the `pagure.lib.model.Project` object of the current pagure
         project browsed
@@ -527,8 +529,7 @@ def request_pull_to_diff_or_patch(
 )
 @login_required
 def request_pull_edit(repo, requestid, username=None, namespace=None):
-    """ Edit the title of a pull-request.
-    """
+    """Edit the title of a pull-request."""
 
     repo = flask.g.repo
 
@@ -553,10 +554,11 @@ def request_pull_edit(repo, requestid, username=None, namespace=None):
             403, description="You are not allowed to edit this pull-request"
         )
 
-    form = pagure.forms.RequestPullForm()
+    form = pagure.forms.RequestPullEditForm(branches=flask.g.branches)
     if form.validate_on_submit():
         request.title = form.title.data.strip()
         request.initial_comment = form.initial_comment.data.strip()
+        request.branch = form.branch_to.data.strip()
         if flask.g.fas_user.username == request.user.username:
             request.allow_rebase = form.allow_rebase.data
         flask.g.session.add(request)
@@ -597,6 +599,7 @@ def request_pull_edit(repo, requestid, username=None, namespace=None):
     elif flask.request.method == "GET":
         form.title.data = request.title
         form.initial_comment.data = request.initial_comment
+        form.branch_to.data = request.branch
 
     return flask.render_template(
         "pull_request_title.html",
@@ -652,8 +655,7 @@ def pull_request_add_comment(
     username=None,
     namespace=None,
 ):
-    """ Add a comment to a commit in a pull-request.
-    """
+    """Add a comment to a commit in a pull-request."""
     repo = flask.g.repo
 
     if not repo.settings.get("pull_requests", True):
@@ -752,8 +754,7 @@ def pull_request_add_comment(
 )
 @login_required
 def pull_request_drop_comment(repo, requestid, username=None, namespace=None):
-    """ Delete a comment of a pull-request.
-    """
+    """Delete a comment of a pull-request."""
     repo = flask.g.repo
 
     if not repo:
@@ -844,8 +845,7 @@ def pull_request_drop_comment(repo, requestid, username=None, namespace=None):
 def pull_request_edit_comment(
     repo, requestid, commentid, username=None, namespace=None
 ):
-    """Edit comment of a pull request
-    """
+    """Edit comment of a pull request"""
     is_js = flask.request.args.get("js", False)
 
     project = flask.g.repo
@@ -940,8 +940,7 @@ def pull_request_edit_comment(
 )
 @login_required
 def reopen_request_pull(repo, requestid, username=None, namespace=None):
-    """ Re-Open a pull request.
-    """
+    """Re-Open a pull request."""
     form = pagure.forms.ConfirmationForm()
     if form.validate_on_submit():
 
@@ -1018,8 +1017,7 @@ def reopen_request_pull(repo, requestid, username=None, namespace=None):
 )
 @login_required
 def ci_trigger_request_pull(repo, requestid, username=None, namespace=None):
-    """ Trigger CI testing for a PR.
-    """
+    """Trigger CI testing for a PR."""
     form = pagure.forms.TriggerCIPRForm()
     if not form.validate_on_submit():
         flask.flash("Invalid input submitted", "error")
@@ -1083,8 +1081,7 @@ def ci_trigger_request_pull(repo, requestid, username=None, namespace=None):
 )
 @login_required
 def merge_request_pull(repo, requestid, username=None, namespace=None):
-    """ Create a pull request with the changes from the fork into the project.
-    """
+    """Create a pull request with the changes from the fork into the project."""
 
     form = pagure.forms.MergePRForm()
     if not form.validate_on_submit():
@@ -1315,8 +1312,7 @@ def merge_request_pull(repo, requestid, username=None, namespace=None):
 )
 @login_required
 def close_request_pull(repo, requestid, username=None, namespace=None):
-    """ Close a pull request without merging it.
-    """
+    """Close a pull request without merging it."""
 
     form = pagure.forms.ConfirmationForm()
     if form.validate_on_submit():
@@ -1384,8 +1380,7 @@ def close_request_pull(repo, requestid, username=None, namespace=None):
 )
 @login_required
 def refresh_request_pull(repo, requestid, username=None, namespace=None):
-    """ Refresh a remote pull request.
-    """
+    """Refresh a remote pull request."""
 
     form = pagure.forms.ConfirmationForm()
     if form.validate_on_submit():
@@ -1452,7 +1447,7 @@ def refresh_request_pull(repo, requestid, username=None, namespace=None):
 )
 @login_required
 def update_pull_requests(repo, requestid, username=None, namespace=None):
-    """ Update the metadata of a pull-request. """
+    """Update the metadata of a pull-request."""
     repo = flask.g.repo
 
     if not repo.settings.get("pull_requests", True):
@@ -1547,8 +1542,7 @@ def update_pull_requests(repo, requestid, username=None, namespace=None):
 @UI_NS.route("/do_fork/fork/<username>/<namespace>/<repo>", methods=["POST"])
 @login_required
 def fork_project(repo, username=None, namespace=None):
-    """ Fork the project specified into the user's namespace
-    """
+    """Fork the project specified into the user's namespace"""
     repo = flask.g.repo
 
     form = pagure.forms.ConfirmationForm()
@@ -1639,8 +1633,7 @@ def fork_project(repo, username=None, namespace=None):
 def new_request_pull(
     repo, branch_to, branch_from, username=None, namespace=None
 ):
-    """ Create a pull request with the changes from the fork into the project.
-    """
+    """Create a pull request with the changes from the fork into the project."""
     branch_to = flask.request.values.get("branch_to", branch_to)
     project_to = flask.request.values.get("project_to")
 
@@ -1851,8 +1844,8 @@ def new_request_pull(
 )
 @login_required
 def new_remote_request_pull(repo, username=None, namespace=None):
-    """ Create a pull request with the changes from a remote fork into the
-        project.
+    """Create a pull request with the changes from a remote fork into the
+    project.
     """
     confirm = flask.request.values.get("confirm", False)
 
@@ -2066,8 +2059,7 @@ def new_remote_request_pull(repo, username=None, namespace=None):
 )
 @login_required
 def fork_edit_file(repo, branchname, filename, username=None, namespace=None):
-    """ Fork the project specified and open the specific file to edit
-    """
+    """Fork the project specified and open the specific file to edit"""
     repo = flask.g.repo
 
     form = pagure.forms.ConfirmationForm()
diff --git a/pagure/ui/groups.py b/pagure/ui/groups.py
index b070e2e..b7db082 100644
--- a/pagure/ui/groups.py
+++ b/pagure/ui/groups.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import logging
 
@@ -22,14 +22,13 @@ from pagure.config import config as pagure_config
 from pagure.ui import UI_NS
 from pagure.utils import login_required
 
-
 _log = logging.getLogger(__name__)
 
 
 @UI_NS.route("/groups/")
 @UI_NS.route("/groups")
 def group_lists():
-    """ List all the groups associated with all the projects. """
+    """List all the groups associated with all the projects."""
 
     group_type = "user"
     if pagure.utils.is_admin():
@@ -56,7 +55,7 @@ def group_lists():
 @UI_NS.route("/group/<group>/", methods=["GET", "POST"])
 @UI_NS.route("/group/<group>", methods=["GET", "POST"])
 def view_group(group):
-    """ Displays information about this group. """
+    """Displays information about this group."""
     if flask.request.method == "POST" and not pagure_config.get(
         "ENABLE_USER_MNGT", True
     ):
@@ -128,7 +127,7 @@ def view_group(group):
 @UI_NS.route("/group/<group>/edit", methods=["GET", "POST"])
 @login_required
 def edit_group(group):
-    """ Allows editing the information about this group. """
+    """Allows editing the information about this group."""
     if not pagure_config.get("ENABLE_USER_MNGT", True):
         flask.abort(404)
 
@@ -183,7 +182,7 @@ def edit_group(group):
 @UI_NS.route("/group/<group>/give", methods=["POST"])
 @login_required
 def give_group(group):
-    """ Allows giving away a group. """
+    """Allows giving away a group."""
     if not pagure_config.get("ENABLE_USER_MNGT", True):
         flask.abort(404)
 
@@ -259,8 +258,7 @@ def give_group(group):
 @UI_NS.route("/group/<group>/<user>/delete", methods=["POST"])
 @login_required
 def group_user_delete(user, group):
-    """ Delete an user from a certain group
-    """
+    """Delete an user from a certain group"""
     if not pagure_config.get("ENABLE_USER_MNGT", True):
         flask.abort(404)
 
@@ -307,8 +305,7 @@ def group_user_delete(user, group):
 @UI_NS.route("/group/<group>/delete", methods=["POST"])
 @login_required
 def group_delete(group):
-    """ Delete a certain group
-    """
+    """Delete a certain group"""
     if not pagure_config.get("ENABLE_USER_MNGT", True):
         flask.abort(404)
 
@@ -350,8 +347,7 @@ def group_delete(group):
 @UI_NS.route("/group/add", methods=["GET", "POST"])
 @login_required
 def add_group():
-    """ Endpoint to create groups
-    """
+    """Endpoint to create groups"""
     if not pagure_config.get("ENABLE_USER_MNGT", True):
         flask.abort(404)
 
diff --git a/pagure/ui/issues.py b/pagure/ui/issues.py
index 4682f34..d46f6ac 100644
--- a/pagure/ui/issues.py
+++ b/pagure/ui/issues.py
@@ -14,38 +14,37 @@
 # pylint: disable=too-many-statements
 
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import datetime
 import logging
 import os
-from collections import defaultdict, OrderedDict
+from collections import OrderedDict, defaultdict
 from math import ceil
 
 import flask
 import pygit2
 import werkzeug.datastructures
-from sqlalchemy.exc import SQLAlchemyError
 from binaryornot.helpers import is_binary_string
+from six.moves.urllib.parse import urljoin
+from sqlalchemy.exc import SQLAlchemyError
 
 import pagure.doc_utils
 import pagure.exceptions
-import pagure.lib.query
-import pagure.lib.mimetype
-from pagure.decorators import has_issue_tracker, is_repo_admin
-
 import pagure.forms
+import pagure.lib.mimetype
+import pagure.lib.query
 from pagure.config import config as pagure_config
+from pagure.decorators import has_issue_tracker, is_repo_admin
 from pagure.ui import UI_NS
 from pagure.utils import (
     __get_file_in_tree,
     authenticated,
+    is_true,
     login_required,
     urlpattern,
-    is_true,
 )
 
-
 _log = logging.getLogger(__name__)
 
 
@@ -76,7 +75,7 @@ _log = logging.getLogger(__name__)
 @login_required
 @has_issue_tracker
 def update_issue(repo, issueid, username=None, namespace=None):
-    """ Add comment or update metadata of an issue. """
+    """Add comment or update metadata of an issue."""
     is_js = flask.request.args.get("js", False)
 
     repo = flask.g.repo
@@ -185,22 +184,29 @@ def update_issue(repo, issueid, username=None, namespace=None):
 
         comment = form.comment.data
         depends = []
-        for depend in form.depending.data.split(","):
-            if depend.strip():
-                try:
-                    depends.append(int(depend.strip()))
-                except ValueError:
-                    pass
+        # This field is optional, check if it's filled first
+        if form.depending.data:
+            for depend in form.depending.data.split(","):
+                if depend.strip():
+                    try:
+                        depends.append(int(depend.strip()))
+                    except ValueError:
+                        pass
 
         blocks = []
-        for block in form.blocking.data.split(","):
-            if block.strip():
-                try:
-                    blocks.append(int(block.strip()))
-                except ValueError:
-                    pass
-
-        assignee = form.assignee.data.strip() or None
+        # Check if the optional field is filled
+        if form.blocking.data:
+            for block in form.blocking.data.split(","):
+                if block.strip():
+                    try:
+                        blocks.append(int(block.strip()))
+                    except ValueError:
+                        pass
+
+        assignee = None
+        # Check if the optional field is filled
+        if form.assignee.data:
+            assignee = form.assignee.data.strip()
         new_status = form.status.data.strip() or None
         close_status = form.close_status.data or None
         if close_status not in repo.close_status:
@@ -211,7 +217,10 @@ def update_issue(repo, issueid, username=None, namespace=None):
             new_priority = int(form.priority.data)
         except (ValueError, TypeError):
             pass
-        tags = [tag.strip() for tag in form.tag.data.split(",") if tag.strip()]
+        tags = []
+        # Check if the optional field is filled
+        if form.tag.data:
+            tags = [tag.strip() for tag in form.tag.data.split(",")]
 
         new_milestone = None
         try:
@@ -515,8 +524,7 @@ def issue_comment_add_reaction(
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/issues")
 @has_issue_tracker
 def view_issues(repo, username=None, namespace=None):
-    """ List all issues associated to a repo
-    """
+    """List all issues associated to a repo"""
 
     status = flask.request.args.get("status", "Open")
     status = flask.request.args.get("close_status") or status
@@ -747,8 +755,7 @@ def view_issues(repo, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/roadmap")
 @has_issue_tracker
 def view_roadmap(repo, username=None, namespace=None):
-    """ List all issues associated to a repo as roadmap
-    """
+    """List all issues associated to a repo as roadmap"""
     milestones_status_arg = flask.request.args.get("status", "active")
     milestones_keyword_arg = flask.request.args.get("keyword", None)
     milestones_onlyincomplete_arg = flask.request.args.get(
@@ -849,8 +856,7 @@ def view_roadmap(repo, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/roadmap/<path:milestone>")
 @has_issue_tracker
 def view_milestone(repo, username=None, namespace=None, milestone=None):
-    """ List all issues associated to a repo as roadmap
-    """
+    """List all issues associated to a repo as roadmap"""
 
     repo = flask.g.repo
 
@@ -908,8 +914,7 @@ def view_milestone(repo, username=None, namespace=None, milestone=None):
 @login_required
 @has_issue_tracker
 def new_issue(repo, username=None, namespace=None):
-    """ Create a new issue
-    """
+    """Create a new issue"""
     template = flask.request.args.get("template") or "default"
     repo = flask.g.repo
     open_access = repo.settings.get("open_metadata_access_to_all", False)
@@ -1112,8 +1117,7 @@ def new_issue(repo, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/issue/<int:issueid>")
 @has_issue_tracker
 def view_issue(repo, issueid, username=None, namespace=None):
-    """ List all issues associated to a repo
-    """
+    """List all issues associated to a repo"""
 
     repo = flask.g.repo
 
@@ -1189,8 +1193,7 @@ def view_issue(repo, issueid, username=None, namespace=None):
 )
 @has_issue_tracker
 def delete_issue(repo, issueid, username=None, namespace=None):
-    """ Delete the specified issue
-    """
+    """Delete the specified issue"""
 
     repo = flask.g.repo
 
@@ -1266,8 +1269,7 @@ def delete_issue(repo, issueid, username=None, namespace=None):
 @login_required
 @has_issue_tracker
 def edit_issue(repo, issueid, username=None, namespace=None):
-    """ Edit the specified issue
-    """
+    """Edit the specified issue"""
     repo = flask.g.repo
 
     issue = pagure.lib.query.search_issues(
@@ -1403,8 +1405,7 @@ def edit_issue(repo, issueid, username=None, namespace=None):
 @login_required
 @has_issue_tracker
 def upload_issue(repo, issueid, username=None, namespace=None):
-    """ Upload a file to a ticket.
-    """
+    """Upload a file to a ticket."""
     repo = flask.g.repo
 
     issue = pagure.lib.query.search_issues(
@@ -1468,7 +1469,7 @@ def upload_issue(repo, issueid, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/issue/raw/<path:filename>")
 @has_issue_tracker
 def view_issue_raw_file(repo, filename=None, username=None, namespace=None):
-    """ Displays the raw content of a file of a commit for the specified
+    """Displays the raw content of a file of a commit for the specified
     ticket repo.
     """
     raw = is_true(flask.request.args.get("raw"))
@@ -1560,8 +1561,7 @@ def view_issue_raw_file(repo, filename=None, username=None, namespace=None):
 def edit_comment_issue(
     repo, issueid, commentid, username=None, namespace=None
 ):
-    """Edit comment of an issue
-    """
+    """Edit comment of an issue"""
     is_js = flask.request.args.get("js", False)
 
     project = flask.g.repo
@@ -1648,14 +1648,13 @@ def edit_comment_issue(
 @login_required
 @is_repo_admin
 def save_reports(repo, username=None, namespace=None):
-    """ Marked for watching or Unwatching
-    """
+    """Marked for watching or Unwatching"""
 
     return_point = flask.url_for(
         "ui_ns.view_issues", repo=repo, username=username, namespace=namespace
     )
     if pagure.utils.is_safe_url(flask.request.referrer):
-        return_point = flask.request.referrer
+        return_point = urljoin(flask.request.host_url, flask.request.referrer)
 
     form = pagure.forms.AddReportForm()
     if not form.validate_on_submit():
@@ -1684,8 +1683,7 @@ def save_reports(repo, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<repo>/report/<report>")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/report/<report>")
 def view_report(repo, report, username=None, namespace=None):
-    """ Show the specified report.
-    """
+    """Show the specified report."""
     reports = flask.g.repo.reports
     if report not in reports:
         flask.abort(404, description="No such report found")
diff --git a/pagure/ui/login.py b/pagure/ui/login.py
index 1a0dbd2..ef6e495 100644
--- a/pagure/ui/login.py
+++ b/pagure/ui/login.py
@@ -9,26 +9,25 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import datetime
 import logging
 
 import flask
-from sqlalchemy.exc import SQLAlchemyError
 from six.moves.urllib.parse import urljoin
+from sqlalchemy.exc import SQLAlchemyError
 
-import pagure.login_forms as forms
 import pagure.config
 import pagure.lib.login
 import pagure.lib.model as model
 import pagure.lib.model_base
 import pagure.lib.notify
 import pagure.lib.query
-from pagure.utils import login_required
-from pagure.lib.login import generate_hashed_value, check_password
+import pagure.login_forms as forms
+from pagure.lib.login import check_password, generate_hashed_value
 from pagure.ui import UI_NS
-
+from pagure.utils import login_required
 
 _log = logging.getLogger(__name__)
 
@@ -36,8 +35,10 @@ _log = logging.getLogger(__name__)
 @UI_NS.route("/user/new/", methods=["GET", "POST"])
 @UI_NS.route("/user/new", methods=["GET", "POST"])
 def new_user():
-    """ Create a new user.
-    """
+    """Create a new user."""
+    if not pagure.config.config.get("ALLOW_USER_REGISTRATION", True):
+        flask.flash("User registration is disabled.", "error")
+        return flask.redirect(flask.url_for("auth_login"))
     form = forms.NewUserForm()
     if form.validate_on_submit():
 
@@ -87,14 +88,15 @@ def new_user():
 
 @UI_NS.route("/dologin", methods=["POST"])
 def do_login():
-    """ Log in the user.
-    """
+    """Log in the user."""
     logout()
 
     form = forms.LoginForm()
     next_url = flask.request.form.get("next_url")
     if not next_url or next_url == "None":
         next_url = flask.url_for("ui_ns.index")
+    else:
+        next_url = urljoin(flask.request.host_url, next_url)
 
     if form.validate_on_submit():
         username = form.username.data
@@ -143,8 +145,7 @@ def do_login():
 @UI_NS.route("/confirm/<token>/")
 @UI_NS.route("/confirm/<token>")
 def confirm_user(token):
-    """ Confirm a user account.
-    """
+    """Confirm a user account."""
     user_obj = pagure.lib.query.search_user(flask.g.session, token=token)
     if not user_obj:
         flask.flash("No user associated with this token.", "error")
@@ -170,7 +171,7 @@ def confirm_user(token):
 @UI_NS.route("/password/lost/", methods=["GET", "POST"])
 @UI_NS.route("/password/lost", methods=["GET", "POST"])
 def lost_password():
-    """ Method to allow a user to change his/her password assuming the email
+    """Method to allow a user to change his/her password assuming the email
     is not compromised.
     """
     form = forms.LostPasswordForm()
@@ -221,8 +222,7 @@ def lost_password():
 @UI_NS.route("/password/reset/<token>/", methods=["GET", "POST"])
 @UI_NS.route("/password/reset/<token>", methods=["GET", "POST"])
 def reset_password(token):
-    """ Method to allow a user to reset his/her password.
-    """
+    """Method to allow a user to reset his/her password."""
     form = forms.ResetPasswordForm()
 
     user_obj = pagure.lib.query.search_user(flask.g.session, token=token)
@@ -267,8 +267,7 @@ def reset_password(token):
 @UI_NS.route("/password/change", methods=["GET", "POST"])
 @login_required
 def change_password():
-    """ Method to change the password for local auth users.
-    """
+    """Method to change the password for local auth users."""
 
     form = forms.ChangePasswordForm()
     user_obj = pagure.lib.query.search_user(
@@ -321,7 +320,7 @@ def change_password():
 
 
 def send_confirmation_email(user):
-    """ Sends the confirmation email asking the user to confirm its email
+    """Sends the confirmation email asking the user to confirm its email
     address.
     """
     if not user.emails:
@@ -332,7 +331,8 @@ def send_confirmation_email(user):
 
     # A link with a secret token to confirm the registration
     confirmation_url = urljoin(
-        instance_url, flask.url_for("ui_ns.confirm_user", token=user.token),
+        instance_url,
+        flask.url_for("ui_ns.confirm_user", token=user.token),
     )
 
     message = """Dear %(username)s,
@@ -364,7 +364,7 @@ Your pagure admin.
 
 
 def send_lostpassword_email(user):
-    """ Sends the email with the information on how to reset his/her password
+    """Sends the email with the information on how to reset his/her password
     to the user.
     """
     if not user.emails:
@@ -405,15 +405,13 @@ Your pagure admin.
 
 
 def logout():
-    """ Log the user out by expiring the user's session.
-    """
+    """Log the user out by expiring the user's session."""
     flask.g.fas_session_id = None
     flask.g.fas_user = None
 
 
 def _check_session_cookie():
-    """ Set the user into flask.g if the user is logged in.
-    """
+    """Set the user into flask.g if the user is logged in."""
     if not hasattr(flask.g, "session") or not flask.g.session:
         flask.g.session = pagure.lib.model_base.create_session(
             flask.current_app.config["DB_URL"]
@@ -466,7 +464,7 @@ def _check_session_cookie():
 
 
 def _send_session_cookie(response):
-    """ Set the session cookie if the user is authenticated. """
+    """Set the session cookie if the user is authenticated."""
     cookie_name = pagure.config.config.get("SESSION_COOKIE_NAME", "pagure")
     secure = pagure.config.config.get("SESSION_COOKIE_SECURE", True)
 
diff --git a/pagure/ui/oidc_login.py b/pagure/ui/oidc_login.py
index eba73e8..8488360 100644
--- a/pagure/ui/oidc_login.py
+++ b/pagure/ui/oidc_login.py
@@ -8,13 +8,15 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import logging
+from base64 import b64decode
 
 import flask
-from flask import Markup
 import munch
+from flask import Markup
+from flask_oidc import OpenIDConnect
 from sqlalchemy.exc import SQLAlchemyError
 
 import pagure.lib.query
@@ -22,8 +24,6 @@ from pagure.config import config as pagure_config
 from pagure.flask_app import logout
 from pagure.utils import is_admin
 
-from flask_oidc import OpenIDConnect
-
 oidc = OpenIDConnect()
 
 
@@ -51,11 +51,19 @@ def fas_user_from_oidc():
                 username = info[email_key].split("@")[0]
             elif fb == "sub":
                 username = flask.g.oidc_id_token["sub"]
+        # The SSH key may be returned base64-encoded
+        ssh_key = info.get(ssh_key)
+        if ssh_key is not None:
+            try:
+                ssh_key = b64decode(ssh_key).decode("ascii")
+            except (TypeError, ValueError):
+                pass
+        # Create the user object
         flask.g.fas_user = munch.Munch(
             username=username,
             fullname=info.get(fulln_key, ""),
             email=info[email_key],
-            ssh_key=info.get(ssh_key),
+            ssh_key=ssh_key,
             groups=info.get(groups_key, []),
             login_time=flask.session["oidc_logintime"],
         )
diff --git a/pagure/ui/plugins.py b/pagure/ui/plugins.py
index 5238051..a7133ec 100644
--- a/pagure/ui/plugins.py
+++ b/pagure/ui/plugins.py
@@ -11,23 +11,21 @@
 
 # pylint: disable=too-many-branches
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import logging
 
 import flask
 from flask import Markup
-
 from sqlalchemy.exc import SQLAlchemyError
 
 import pagure.exceptions
 import pagure.forms
 import pagure.lib.plugins
+from pagure.decorators import is_repo_admin
 from pagure.exceptions import FileNotFoundException
 from pagure.ui import UI_NS
 from pagure.utils import login_required
-from pagure.decorators import is_repo_admin
-
 
 _log = logging.getLogger(__name__)
 
@@ -78,8 +76,7 @@ _log = logging.getLogger(__name__)
 @login_required
 @is_repo_admin
 def view_plugin(repo, plugin, username=None, namespace=None, full=True):
-    """ Presents the settings of the project.
-    """
+    """Presents the settings of the project."""
     repo = flask.g.repo
 
     # Private repos are not allowed to leak information outside so disabling CI
diff --git a/pagure/ui/repo.py b/pagure/ui/repo.py
index b6aacc8..fcb7f4a 100644
--- a/pagure/ui/repo.py
+++ b/pagure/ui/repo.py
@@ -17,7 +17,7 @@
 # pylint: disable=broad-except
 
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import datetime
 import json
@@ -27,48 +27,47 @@ import re
 from math import ceil
 
 import flask
-import pygit2
 import kitchen.text.converters as ktc
+import pygit2
 import six
 import werkzeug.utils
-
-from six import BytesIO
+from binaryornot.helpers import is_binary_string
 from PIL import Image
+from six import BytesIO
+from six.moves.urllib.parse import urljoin
 from sqlalchemy.exc import SQLAlchemyError
 
-from binaryornot.helpers import is_binary_string
-
 import pagure.exceptions
+import pagure.forms
 import pagure.lib.git
 import pagure.lib.mimetype
 import pagure.lib.plugins
 import pagure.lib.query
 import pagure.lib.tasks
-import pagure.forms
 import pagure.ui.plugins
 from pagure.config import config as pagure_config
+from pagure.decorators import (
+    has_issue_or_pr_enabled,
+    has_issue_tracker,
+    has_pr_enabled,
+    is_admin_sess_timedout,
+    is_repo_admin,
+)
 from pagure.flask_app import _get_user
 from pagure.lib import encoding_utils
 from pagure.ui import UI_NS
 from pagure.utils import (
     __get_file_in_tree,
-    login_required,
     is_true,
+    login_required,
     stream_template,
 )
-from pagure.decorators import (
-    is_repo_admin,
-    is_admin_sess_timedout,
-    has_issue_tracker,
-    has_issue_or_pr_enabled,
-    has_pr_enabled,
-)
 
 _log = logging.getLogger(__name__)
 
 
 def get_preferred_readme(tree):
-    """ Establish some order about which README gets displayed
+    """Establish some order about which README gets displayed
     if there are several in the repository. If none of the listed
     README files is availabe, display either the next file that
     starts with 'README' or nothing at all.
@@ -90,7 +89,7 @@ def get_preferred_readme(tree):
 @UI_NS.route("/fork/<username>/<repo>.git")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>.git")
 def view_repo_git(repo, username=None, namespace=None):
-    """ Redirect to the project index page when user wants to view
+    """Redirect to the project index page when user wants to view
     the git repo of the project
     """
     return flask.redirect(
@@ -112,8 +111,7 @@ def view_repo_git(repo, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>")
 def view_repo(repo, username=None, namespace=None):
-    """ Front page of a specific repo.
-    """
+    """Front page of a specific repo."""
     repo_db = flask.g.repo
     repo_obj = flask.g.repo_obj
 
@@ -297,8 +295,7 @@ def view_repo_branch(repo, branchname, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/commits")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/commits/<path:branchname>")
 def view_commits(repo, branchname=None, username=None, namespace=None):
-    """ Displays the commits of the specified repo.
-    """
+    """Displays the commits of the specified repo."""
     repo = flask.g.repo
     repo_obj = flask.g.repo_obj
 
@@ -442,8 +439,7 @@ def view_commits(repo, branchname=None, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/c/<commit1>..<commit2>/")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/c/<commit1>..<commit2>")
 def compare_commits(repo, commit1, commit2, username=None, namespace=None):
-    """ Compares two commits for specified repo
-    """
+    """Compares two commits for specified repo"""
     repo = flask.g.repo
     repo_obj = flask.g.repo_obj
 
@@ -513,8 +509,7 @@ def compare_commits(repo, commit1, commit2, username=None, namespace=None):
     "<path:filename>"
 )
 def view_file(repo, identifier, filename, username=None, namespace=None):
-    """ Displays the content of a file or a tree for the specified repo.
-    """
+    """Displays the content of a file or a tree for the specified repo."""
     repo = flask.g.repo
     repo_obj = flask.g.repo_obj
 
@@ -639,7 +634,13 @@ def view_file(repo, identifier, filename, username=None, namespace=None):
         output_type = "tree"
 
     if output_type == "binary":
-        headers[str("Content-Disposition")] = "attachment"
+        return view_raw_file(
+            repo,
+            identifier,
+            filename=filename,
+            username=username,
+            namespace=namespace,
+        )
 
     return flask.Response(
         flask.stream_with_context(
@@ -679,8 +680,7 @@ def view_file(repo, identifier, filename, username=None, namespace=None):
 def view_raw_file(
     repo, identifier, filename=None, username=None, namespace=None
 ):
-    """ Displays the raw content of a file of a commit for the specified repo.
-    """
+    """Displays the raw content of a file of a commit for the specified repo."""
     repo_obj = flask.g.repo_obj
 
     if repo_obj.is_empty:
@@ -737,8 +737,7 @@ def view_raw_file(
 @UI_NS.route("/fork/<username>/<repo>/blame/<path:filename>")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/blame/<path:filename>")
 def view_blame_file(repo, filename, username=None, namespace=None):
-    """ Displays the blame of a file or a tree for the specified repo.
-    """
+    """Displays the blame of a file or a tree for the specified repo."""
     repo = flask.g.repo
     repo_obj = flask.g.repo_obj
 
@@ -820,8 +819,7 @@ def view_blame_file(repo, filename, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<repo>/history/<path:filename>")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/history/<path:filename>")
 def view_history_file(repo, filename, username=None, namespace=None):
-    """ Displays the history of a file or a tree for the specified repo.
-    """
+    """Displays the history of a file or a tree for the specified repo."""
     repo = flask.g.repo
     repo_obj = flask.g.repo_obj
 
@@ -874,8 +872,7 @@ def view_history_file(repo, filename, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/c/<commitid>/")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/c/<commitid>")
 def view_commit(repo, commitid, username=None, namespace=None):
-    """ Render a commit in a repo
-    """
+    """Render a commit in a repo"""
     repo = flask.g.repo
     if not repo:
         flask.abort(404, description="Project not found")
@@ -947,8 +944,7 @@ def view_commit(repo, commitid, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<repo>/c/<commitid>.patch")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/c/<commitid>.patch")
 def view_commit_patch(repo, commitid, username=None, namespace=None):
-    """ Render a commit in a repo as patch
-    """
+    """Render a commit in a repo as patch"""
     return view_commit_patch_or_diff(
         repo, commitid, username, namespace, diff=False
     )
@@ -959,8 +955,7 @@ def view_commit_patch(repo, commitid, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<repo>/c/<commitid>.diff")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/c/<commitid>.diff")
 def view_commit_diff(repo, commitid, username=None, namespace=None):
-    """ Render a commit in a repo as diff
-    """
+    """Render a commit in a repo as diff"""
 
     is_js = is_true(flask.request.args.get("js"))
 
@@ -972,7 +967,7 @@ def view_commit_diff(repo, commitid, username=None, namespace=None):
 def view_commit_patch_or_diff(
     repo, commitid, username=None, namespace=None, diff=False, is_js=False
 ):
-    """ Renders a commit either as a patch or as a diff. """
+    """Renders a commit either as a patch or as a diff."""
 
     repo_obj = flask.g.repo_obj
 
@@ -1026,8 +1021,7 @@ def view_commit_patch_or_diff(
 @UI_NS.route("/fork/<username>/<repo>/tree/<path:identifier>")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/tree/<path:identifier>")
 def view_tree(repo, identifier=None, username=None, namespace=None):
-    """ Render the tree of the repo
-    """
+    """Render the tree of the repo"""
     repo = flask.g.repo
     repo_obj = flask.g.repo_obj
 
@@ -1105,8 +1099,7 @@ def view_tree(repo, identifier=None, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/releases/")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/releases")
 def view_tags(repo, username=None, namespace=None):
-    """ Presents all the tags of the project.
-    """
+    """Presents all the tags of the project."""
     repo = flask.g.repo
     tags = pagure.lib.git.get_git_tags_objects(repo)
 
@@ -1134,8 +1127,7 @@ def view_tags(repo, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/branches/")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/branches")
 def view_branches(repo, username=None, namespace=None):
-    """ Branches
-    """
+    """Branches"""
     repo_db = flask.g.repo
     repo_obj = flask.g.repo_obj
 
@@ -1169,8 +1161,7 @@ def view_branches(repo, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/forks/")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/forks")
 def view_forks(repo, username=None, namespace=None):
-    """ Forks
-    """
+    """Forks"""
 
     return flask.render_template(
         "repo_forks.html", select="forks", username=username, repo=flask.g.repo
@@ -1192,8 +1183,7 @@ def view_forks(repo, username=None, namespace=None):
 @login_required
 @is_repo_admin
 def new_release(repo, username=None, namespace=None):
-    """ Upload a new release.
-    """
+    """Upload a new release."""
     if not pagure_config.get("UPLOAD_FOLDER_PATH") or not pagure_config.get(
         "UPLOAD_FOLDER_URL"
     ):
@@ -1275,8 +1265,7 @@ def new_release(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def view_settings(repo, username=None, namespace=None):
-    """ Presents the settings of the project.
-    """
+    """Presents the settings of the project."""
 
     repo = flask.g.repo
     repo_obj = flask.g.repo_obj
@@ -1286,6 +1275,8 @@ def view_settings(repo, username=None, namespace=None):
     )
     tags = pagure.lib.query.get_tags_of_project(flask.g.session, repo)
 
+    branch_aliases = pagure.lib.git.get_branch_aliases(repo)
+
     form = pagure.forms.ConfirmationForm()
     tag_form = pagure.forms.AddIssueTagForm()
 
@@ -1351,6 +1342,7 @@ def view_settings(repo, username=None, namespace=None):
         plugins=plugins,
         branchname=branchname,
         pagure_admin=pagure.utils.is_admin(),
+        branch_aliases=branch_aliases,
     )
 
 
@@ -1367,7 +1359,7 @@ def view_settings(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def test_web_hook(repo, username=None, namespace=None):
-    """ Endpoint that can be called to send a test message to the web-hook
+    """Endpoint that can be called to send a test message to the web-hook
     service allowing to test the web-hooks set.
     """
 
@@ -1403,8 +1395,7 @@ def test_web_hook(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def update_project(repo, username=None, namespace=None):
-    """ Update the description of a project.
-    """
+    """Update the description of a project."""
 
     repo = flask.g.repo
 
@@ -1414,18 +1405,24 @@ def update_project(repo, username=None, namespace=None):
 
         try:
             repo.description = form.description.data
-            repo.avatar_email = form.avatar_email.data.strip()
-            repo.url = form.url.data.strip()
+            # Check if the optional value is filled
+            if form.avatar_email.data is not None:
+                repo.avatar_email = form.avatar_email.data.strip()
+            # Check if the optional value is filled
+            if form.url.data:
+                repo.url = form.url.data.strip()
             if repo.private:
                 repo.private = form.private.data
             if repo.mirrored_from:
                 repo.mirrored_from = form.mirrored_from.data
-            pagure.lib.query.update_tags(
-                flask.g.session,
-                repo,
-                tags=[t.strip() for t in form.tags.data.split(",")],
-                username=flask.g.fas_user.username,
-            )
+            # Check if the optional value is filled
+            if form.tags.data:
+                pagure.lib.query.update_tags(
+                    flask.g.session,
+                    repo,
+                    tags=[t.strip() for t in form.tags.data.split(",")],
+                    username=flask.g.fas_user.username,
+                )
             flask.g.session.add(repo)
             flask.g.session.commit()
             flask.flash("Project updated")
@@ -1462,8 +1459,7 @@ def update_project(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def update_priorities(repo, username=None, namespace=None):
-    """ Update the priorities of a project.
-    """
+    """Update the priorities of a project."""
 
     repo = flask.g.repo
 
@@ -1561,8 +1557,7 @@ def update_priorities(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def default_priority(repo, username=None, namespace=None):
-    """ Update the default priority of a project.
-    """
+    """Update the default priority of a project."""
 
     repo = flask.g.repo
 
@@ -1607,8 +1602,7 @@ def default_priority(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def update_milestones(repo, username=None, namespace=None):
-    """ Update the milestones of a project.
-    """
+    """Update the milestones of a project."""
 
     repo = flask.g.repo
 
@@ -1693,8 +1687,7 @@ def update_milestones(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def change_ref_head(repo, username=None, namespace=None):
-    """ Change HEAD reference
-    """
+    """Change HEAD reference"""
 
     repo = flask.g.repo
     repo_obj = flask.g.repo_obj
@@ -1729,8 +1722,7 @@ def change_ref_head(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def delete_repo(repo, username=None, namespace=None):
-    """ Delete the present project.
-    """
+    """Delete the present project."""
     repo = flask.g.repo
 
     del_project = pagure_config.get("ENABLE_DEL_PROJECTS", True)
@@ -1775,8 +1767,7 @@ def delete_repo(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def new_repo_hook_token(repo, username=None, namespace=None):
-    """ Re-generate a hook token for the present project.
-    """
+    """Re-generate a hook token for the present project."""
     if not pagure_config.get("WEBHOOK", False):
         flask.abort(404)
 
@@ -1819,8 +1810,7 @@ def new_repo_hook_token(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def remove_deploykey(repo, keyid, username=None, namespace=None):
-    """ Remove the specified deploy key from the project.
-    """
+    """Remove the specified deploy key from the project."""
 
     if not pagure_config.get("DEPLOY_KEY", True):
         flask.abort(
@@ -1894,8 +1884,7 @@ def remove_deploykey(repo, keyid, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def remove_user(repo, userid, username=None, namespace=None):
-    """ Remove the specified user from the project.
-    """
+    """Remove the specified user from the project."""
 
     if not pagure_config.get("ENABLE_USER_MNGT", True):
         flask.abort(
@@ -1963,8 +1952,7 @@ def remove_user(repo, userid, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def add_deploykey(repo, username=None, namespace=None):
-    """ Add the specified deploy key to the project.
-    """
+    """Add the specified deploy key to the project."""
 
     if not pagure_config.get("DEPLOY_KEY", True):
         flask.abort(
@@ -2040,8 +2028,7 @@ def add_deploykey(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def add_user(repo, username=None, namespace=None):
-    """ Add the specified user to the project.
-    """
+    """Add the specified user to the project."""
 
     if not pagure_config.get("ENABLE_USER_MNGT", True):
         flask.abort(
@@ -2127,8 +2114,7 @@ def add_user(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def remove_group_project(repo, groupid, username=None, namespace=None):
-    """ Remove the specified group from the project.
-    """
+    """Remove the specified group from the project."""
 
     if not pagure_config.get("ENABLE_USER_MNGT", True):
         flask.abort(
@@ -2157,11 +2143,24 @@ def remove_group_project(repo, groupid, username=None, namespace=None):
                 + "#usersgroups-tab"
             )
 
+        removed_groups = []
         for grp in repo.groups:
             if grp.id == groupid:
+                removed_groups.append(grp.group_name)
                 repo.groups.remove(grp)
                 break
         try:
+            # Commit so the JSON sent on the notification is up to date
+            flask.g.session.commit()
+            pagure.lib.notify.log(
+                repo,
+                topic="project.group.removed",
+                msg=dict(
+                    project=repo.to_json(public=True),
+                    removed_groups=removed_groups,
+                    agent=flask.g.fas_user.username,
+                ),
+            )
             # Mark the project as read_only, celery will unmark it
             pagure.lib.query.update_read_only_mode(
                 flask.g.session, repo, read_only=True
@@ -2201,8 +2200,7 @@ def remove_group_project(repo, groupid, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def add_group_project(repo, username=None, namespace=None):
-    """ Add the specified group to the project.
-    """
+    """Add the specified group to the project."""
 
     if not pagure_config.get("ENABLE_USER_MNGT", True):
         flask.abort(
@@ -2286,8 +2284,7 @@ def add_group_project(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def regenerate_git(repo, username=None, namespace=None):
-    """ Regenerate the specified git repo with the content in the project.
-    """
+    """Regenerate the specified git repo with the content in the project."""
 
     repo = flask.g.repo
 
@@ -2356,8 +2353,7 @@ def regenerate_git(repo, username=None, namespace=None):
 @login_required
 @is_admin_sess_timedout
 def add_token(repo, username=None, namespace=None):
-    """ Add a token to a specified project.
-    """
+    """Add a token to a specified project."""
 
     repo = flask.g.repo
 
@@ -2426,8 +2422,7 @@ def add_token(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def renew_api_token(repo, token_id, username=None, namespace=None):
-    """ Renew a token to a specified project.
-    """
+    """Renew a token to a specified project."""
 
     repo = flask.g.repo
 
@@ -2496,8 +2491,7 @@ def renew_api_token(repo, token_id, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def revoke_api_token(repo, token_id, username=None, namespace=None):
-    """ Revokie a token to a specified project.
-    """
+    """Revokie a token to a specified project."""
 
     repo = flask.g.repo
 
@@ -2557,8 +2551,7 @@ def revoke_api_token(repo, token_id, username=None, namespace=None):
 )
 @login_required
 def edit_file(repo, branchname, filename, username=None, namespace=None):
-    """ Edit a file online.
-    """
+    """Edit a file online."""
     repo = flask.g.repo
     repo_obj = flask.g.repo_obj
 
@@ -2666,8 +2659,7 @@ def edit_file(repo, branchname, filename, username=None, namespace=None):
 )
 @login_required
 def delete_branch(repo, branchname, username=None, namespace=None):
-    """ Delete the branch of a project.
-    """
+    """Delete the branch of a project."""
     if not flask.g.repo.is_fork and not pagure_config.get(
         "ALLOW_DELETE_BRANCH", True
     ):
@@ -2688,9 +2680,15 @@ def delete_branch(repo, branchname, username=None, namespace=None):
     if six.PY2:
         branchname = branchname.encode("utf-8")
 
-    if branchname == "master":
+    default_branchname = "master"
+    if not repo_obj.is_empty and not repo_obj.head_is_unborn:
+        default_branchname = repo_obj.head.shorthand
+
+    if branchname == default_branchname:
         flask.abort(
-            403, description="You are not allowed to delete the master branch"
+            403,
+            description="You are not allowed to delete the default "
+            "branch: %s" % default_branchname,
         )
 
     if branchname not in repo_obj.listall_branches():
@@ -2711,8 +2709,7 @@ def delete_branch(repo, branchname, username=None, namespace=None):
 @UI_NS.route("/docs/fork/<username>/<repo>/")
 @UI_NS.route("/docs/fork/<username>/<namespace>/<repo>/<path:filename>")
 def view_docs(repo, username=None, filename=None, namespace=None):
-    """ Display the documentation
-    """
+    """Display the documentation"""
     repo = flask.g.repo
 
     if not pagure_config.get("DOC_APP_URL"):
@@ -2733,8 +2730,7 @@ def view_docs(repo, username=None, filename=None, namespace=None):
 @UI_NS.route("/<namespace>/<repo>/activity/")
 @UI_NS.route("/<namespace>/<repo>/activity")
 def view_project_activity(repo, namespace=None):
-    """ Display the activity feed
-    """
+    """Display the activity feed"""
 
     if not pagure_config.get("DATAGREPPER_URL"):
         flask.abort(404)
@@ -2749,7 +2745,7 @@ def view_project_activity(repo, namespace=None):
 @UI_NS.route("/<namespace>/<repo>/stargazers/")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/stargazers/")
 def view_stargazers(repo, username=None, namespace=None):
-    """ View all the users who have starred the project """
+    """View all the users who have starred the project"""
 
     stargazers = flask.g.repo.stargazers
     users = [star.user for star in stargazers]
@@ -2770,7 +2766,7 @@ def view_stargazers(repo, username=None, namespace=None):
 )
 @login_required
 def star_project(repo, star, username=None, namespace=None):
-    """ Star or Unstar a project
+    """Star or Unstar a project
 
     :arg repo: string representing the project which has to be starred or
     unstarred.
@@ -2784,7 +2780,7 @@ def star_project(repo, star, username=None, namespace=None):
     if flask.request.referrer is not None and pagure.utils.is_safe_url(
         flask.request.referrer
     ):
-        return_point = flask.request.referrer
+        return_point = urljoin(flask.request.host_url, flask.request.referrer)
 
     form = pagure.forms.ConfirmationForm()
     if not form.validate_on_submit():
@@ -2819,12 +2815,11 @@ def star_project(repo, star, username=None, namespace=None):
 )
 @login_required
 def watch_repo(repo, watch, username=None, namespace=None):
-    """ Marked for watching or unwatching
-    """
+    """Marked for watching or unwatching"""
 
     return_point = flask.url_for("ui_ns.index")
     if pagure.utils.is_safe_url(flask.request.referrer):
-        return_point = flask.request.referrer
+        return_point = urljoin(flask.request.host_url, flask.request.referrer)
 
     form = pagure.forms.ConfirmationForm()
     if not form.validate_on_submit():
@@ -2856,8 +2851,7 @@ def watch_repo(repo, watch, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def update_public_notifications(repo, username=None, namespace=None):
-    """ Update the public notification settings of a project.
-    """
+    """Update the public notification settings of a project."""
 
     repo = flask.g.repo
 
@@ -2910,8 +2904,7 @@ def update_public_notifications(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def update_close_status(repo, username=None, namespace=None):
-    """ Update the close_status of a project.
-    """
+    """Update the close_status of a project."""
 
     repo = flask.g.repo
 
@@ -2956,8 +2949,7 @@ def update_close_status(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def update_quick_replies(repo, username=None, namespace=None):
-    """ Update the quick_replies of a project.
-    """
+    """Update the quick_replies of a project."""
 
     repo = flask.g.repo
 
@@ -3005,8 +2997,7 @@ def update_quick_replies(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def update_custom_keys(repo, username=None, namespace=None):
-    """ Update the custom_keys of a project.
-    """
+    """Update the custom_keys of a project."""
 
     repo = flask.g.repo
 
@@ -3070,8 +3061,7 @@ def update_custom_keys(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def delete_report(repo, username=None, namespace=None):
-    """ Delete a report from a project.
-    """
+    """Delete a report from a project."""
 
     repo = flask.g.repo
 
@@ -3114,8 +3104,7 @@ def delete_report(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def move_to_repospanner(repo, username=None, namespace=None):
-    """ Give a project to someone else.
-    """
+    """Give a project to someone else."""
     repo = flask.g.repo
 
     if not pagure.utils.is_admin():
@@ -3178,8 +3167,7 @@ def move_to_repospanner(repo, username=None, namespace=None):
 @is_admin_sess_timedout
 @is_repo_admin
 def give_project(repo, username=None, namespace=None):
-    """ Give a project to someone else.
-    """
+    """Give a project to someone else."""
     if not pagure_config.get("ENABLE_GIVE_PROJECTS", True):
         flask.abort(404)
 
@@ -3278,7 +3266,7 @@ def give_project(repo, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/dowait/")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/dowait")
 def project_dowait(repo, username=None, namespace=None):
-    """ Schedules a task that just waits 10 seconds for testing locking.
+    """Schedules a task that just waits 10 seconds for testing locking.
 
     This is not available unless ALLOW_PROJECT_DOWAIT is set to True, which
     should only ever be done in test instances.
@@ -3302,8 +3290,7 @@ def project_dowait(repo, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/stats/")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/stats")
 def view_stats(repo, username=None, namespace=None):
-    """ Displays some statistics about the specified repo.
-    """
+    """Displays some statistics about the specified repo."""
     return flask.render_template(
         "repo_stats.html", select="stats", username=username, repo=flask.g.repo
     )
@@ -3315,8 +3302,7 @@ def view_stats(repo, username=None, namespace=None):
 @is_repo_admin
 @has_issue_or_pr_enabled
 def update_tags(repo, username=None, namespace=None):
-    """ Update the tags of a project.
-    """
+    """Update the tags of a project."""
 
     repo = flask.g.repo
 
@@ -3420,8 +3406,7 @@ def update_tags(repo, username=None, namespace=None):
 @is_repo_admin
 @has_issue_or_pr_enabled
 def remove_tag(repo, username=None, namespace=None):
-    """ Remove the specified tag, associated with the issues, from the project.
-    """
+    """Remove the specified tag, associated with the issues, from the project"""
     repo = flask.g.repo
 
     form = pagure.forms.DeleteIssueTagForm()
@@ -3473,8 +3458,7 @@ def remove_tag(repo, username=None, namespace=None):
 @is_repo_admin
 @has_issue_or_pr_enabled
 def edit_tag(repo, tag, username=None, namespace=None):
-    """ Edit the specified tag associated with the issues of a project.
-    """
+    """Edit the specified tag associated with the issues of a project."""
     repo = flask.g.repo
 
     tags = pagure.lib.query.get_tags_of_project(flask.g.session, repo)
@@ -3539,8 +3523,7 @@ def edit_tag(repo, tag, username=None, namespace=None):
 @UI_NS.route("/fork/<username>/<repo>/archive/<ref>/<name>.tar")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/archive/<ref>/<name>.tar")
 def get_project_archive_tar(repo, ref, name, namespace=None, username=None):
-    """ Generate an archive or redirect the user to where it already exists
-    """
+    """Generate an archive or redirect the user to where it already exists"""
 
     return generate_project_archive(
         repo,
@@ -3557,8 +3540,7 @@ def get_project_archive_tar(repo, ref, name, namespace=None, username=None):
 @UI_NS.route("/fork/<username>/<repo>/archive/<ref>/<name>.tar.gz")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/archive/<ref>/<name>.tar.gz")
 def get_project_archive_tar_gz(repo, ref, name, namespace=None, username=None):
-    """ Generate an archive or redirect the user to where it already exists
-    """
+    """Generate an archive or redirect the user to where it already exists"""
     return generate_project_archive(
         repo,
         ref,
@@ -3574,8 +3556,7 @@ def get_project_archive_tar_gz(repo, ref, name, namespace=None, username=None):
 @UI_NS.route("/fork/<username>/<repo>/archive/<ref>/<name>.zip")
 @UI_NS.route("/fork/<username>/<namespace>/<repo>/archive/<ref>/<name>.zip")
 def get_project_archive_zip(repo, ref, name, namespace=None, username=None):
-    """ Generate an archive or redirect the user to where it already exists
-    """
+    """Generate an archive or redirect the user to where it already exists"""
     return generate_project_archive(
         repo,
         ref,
@@ -3589,7 +3570,7 @@ def get_project_archive_zip(repo, ref, name, namespace=None, username=None):
 def generate_project_archive(
     repo, ref, name, extension, namespace=None, username=None
 ):
-    """ Generate an archive or redirect the user to where it already
+    """Generate an archive or redirect the user to where it already
     exists.
     """
 
diff --git a/pagure/utils.py b/pagure/utils.py
index 24a0ce4..61693f9 100644
--- a/pagure/utils.py
+++ b/pagure/utils.py
@@ -8,7 +8,7 @@
 
 """
 
-from __future__ import unicode_literals, absolute_import
+from __future__ import absolute_import, unicode_literals
 
 import datetime
 import fnmatch
@@ -16,21 +16,20 @@ import logging
 import logging.config
 import os
 import re
-from six.moves.urllib.parse import urlparse, urljoin
 from functools import wraps
 
 import flask
 import pygit2
 import six
 import werkzeug.utils
+from six.moves.urllib.parse import urljoin, urlparse
 
+from pagure.config import config as pagure_config
 from pagure.exceptions import (
-    PagureException,
-    InvalidTimestampException,
     InvalidDateformatException,
+    InvalidTimestampException,
+    PagureException,
 )
-from pagure.config import config as pagure_config
-
 
 _log = logging.getLogger(__name__)
 LOGGER_SETUP = False
@@ -49,8 +48,7 @@ def set_up_logging(app=None, force=False, configkey="LOGGING"):
 
 
 def authenticated():
-    """ Utility function checking if the current user is logged in or not.
-    """
+    """Utility function checking if the current user is logged in or not."""
     fas_user = None
     try:
         fas_user = flask.g.fas_user
@@ -61,7 +59,7 @@ def authenticated():
 
 
 def api_authenticated():
-    """ Utility function checking if the current user is logged in or not
+    """Utility function checking if the current user is logged in or not
     in the API.
     """
     return (
@@ -73,7 +71,7 @@ def api_authenticated():
 
 
 def check_api_acls(acls, optional=False):
-    """ Checks if the user provided an API token with its request and if
+    """Checks if the user provided an API token with its request and if
     this token allows the user to access the endpoint desired.
 
     :arg acls: A list of access control
@@ -155,19 +153,20 @@ def check_api_acls(acls, optional=False):
 
 
 def is_safe_url(target):  # pragma: no cover
-    """ Checks that the target url is safe and sending to the current
+    """Checks that the target url is safe and sending to the current
     website not some other malicious one.
     """
     ref_url = urlparse(flask.request.host_url)
     test_url = urlparse(urljoin(flask.request.host_url, target))
     return (
-        test_url.scheme in ("http", "https")
+        target is not None
+        and test_url.scheme in ("http", "https")
         and ref_url.netloc == test_url.netloc
     )
 
 
 def is_admin():
-    """ Return whether the user is admin for this application or not. """
+    """Return whether the user is admin for this application or not."""
     if not authenticated():
         return False
 
@@ -194,7 +193,7 @@ def is_admin():
 
 
 def is_repo_admin(repo_obj, username=None):
-    """ Return whether the user is an admin of the provided repo. """
+    """Return whether the user is an admin of the provided repo."""
     if not authenticated():
         return False
 
@@ -216,7 +215,7 @@ def is_repo_admin(repo_obj, username=None):
 
 
 def is_repo_committer(repo_obj, username=None, session=None):
-    """ Return whether the user is a committer of the provided repo. """
+    """Return whether the user is a committer of the provided repo."""
     import pagure.lib.query
 
     usergroups = set()
@@ -271,8 +270,8 @@ def is_repo_committer(repo_obj, username=None, session=None):
 
 
 def is_repo_collaborator(repo_obj, refname, username=None, session=None):
-    """ Return whether the user has commit on the specified branch of the
-    provided repo. """
+    """Return whether the user has commit on the specified branch of the
+    provided repo."""
     committer = is_repo_committer(repo_obj, username=username, session=session)
     if committer:
         _log.debug("User is a committer")
@@ -311,7 +310,7 @@ def is_repo_collaborator(repo_obj, refname, username=None, session=None):
                     return True
 
     # If they are in a group that has commit access -> maybe
-    for project_group in repo_obj.collaborator_groups:
+    for project_group in repo_obj.collaborator_project_groups:
         if project_group.group.group_name in usergroups:
             # if branch is None when the user tries to read,
             # so we'll allow that
@@ -328,7 +327,7 @@ def is_repo_collaborator(repo_obj, refname, username=None, session=None):
 
 
 def is_repo_user(repo_obj, username=None):
-    """ Return whether the user has some access in the provided repo. """
+    """Return whether the user has some access in the provided repo."""
     if username:
         user = username
     else:
@@ -349,8 +348,8 @@ def is_repo_user(repo_obj, username=None):
 
 
 def get_user_repo_access(repo_obj, username):
-    """ return a string of the highest level of access
-        a user has on a repo.
+    """return a string of the highest level of access
+    a user has on a repo.
     """
     if repo_obj.user.username == username:
         return "main admin"
@@ -368,14 +367,14 @@ def get_user_repo_access(repo_obj, username):
 
 
 def login_required(function):
-    """ Flask decorator to retrict access to logged in user.
+    """Flask decorator to retrict access to logged in user.
     If the auth system is ``fas`` it will also require that the user sign
     the FPCA.
     """
 
     @wraps(function)
     def decorated_function(*args, **kwargs):
-        """ Decorated function, actually does the work. """
+        """Decorated function, actually does the work."""
         auth_method = pagure_config.get("PAGURE_AUTH", None)
         if flask.session.get("_justloggedout", False):
             return flask.redirect(flask.url_for("ui_ns.index"))
@@ -387,8 +386,8 @@ def login_required(function):
             flask.session["_requires_fpca"] = True
             flask.flash(
                 flask.Markup(
-                    'You must <a href="https://admin.fedoraproject'
-                    '.org/accounts/">sign the FPCA</a> (Fedora Project '
+                    'You must <a href="https://accounts.fedoraproject'
+                    '.org/">sign the FPCA</a> (Fedora Project '
                     "Contributor Agreement) to use pagure"
                 ),
                 "errors",
@@ -400,7 +399,7 @@ def login_required(function):
 
 
 def __get_file_in_tree(repo_obj, tree, filepath, bail_on_tree=False):
-    """ Retrieve the entry corresponding to the provided filename in a
+    """Retrieve the entry corresponding to the provided filename in a
     given tree.
     """
 
@@ -643,18 +642,19 @@ ssh_urlpattern = re.compile(ssh_urlregex)
 
 
 def get_repo_path(repo):
-    """ Return the path of the git repository corresponding to the provided
+    """Return the path of the git repository corresponding to the provided
     Repository object from the DB.
     """
     repopath = repo.repopath("main")
     if not os.path.exists(repopath):
+        _log.debug("Git repo not found at: %s", repopath)
         flask.abort(404, description="No git repo found")
 
     return repopath
 
 
 def get_remote_repo_path(remote_git, branch_from, ignore_non_exist=False):
-    """ Return the path of the remote git repository corresponding to the
+    """Return the path of the remote git repository corresponding to the
     provided information.
     """
     repopath = os.path.join(
@@ -725,7 +725,7 @@ def split_project_fullname(project_name):
 
 
 def get_parent_repo_path(repo, repotype="main"):
-    """ Return the path of the parent git repository corresponding to the
+    """Return the path of the parent git repository corresponding to the
     provided Repository object from the DB.
     """
     if repo.parent:
@@ -754,7 +754,7 @@ def is_true(value, trueish=("1", "true", "t", "y")):
 
 
 def validate_date(input_date, allow_empty=False):
-    """ Validate a given time.
+    """Validate a given time.
     The time can either be given as an unix timestamp or using the
     yyyy-mm-dd format.
     If either fail to parse, we raise a 400 error
@@ -779,7 +779,7 @@ def validate_date(input_date, allow_empty=False):
 
 
 def validate_date_range(value):
-    """ Validate a given date range specified using the format since..until.
+    """Validate a given date range specified using the format since..until.
     If .. is not present in the range, it is assumed that only since was
     provided.
     """
@@ -837,7 +837,7 @@ def get_merge_options(request, merge_status):
 
 
 def lookup_deploykey(project, username):
-    """ Finds the Deploy Key specified by the username.
+    """Finds the Deploy Key specified by the username.
 
     Args:
         project (model.Project): The project to look in
@@ -862,7 +862,7 @@ def lookup_deploykey(project, username):
 
 
 def project_has_hook_attr_value(project, hook, attr, value):
-    """ Finds out if project's hook has attribute of given value.
+    """Finds out if project's hook has attribute of given value.
 
     :arg project: The project to inspect
     :type project: pagure.lib.model.Project
diff --git a/requirements.txt b/requirements.txt
index fa53867..867efa9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,9 +10,7 @@ celery
 chardet
 cryptography
 docutils
-enum34;python_version<"3.4"
-# this is only needed with wtforms on py3
-email_validator;python_version>="3.0"
+email_validator
 flask
 flask-wtf
 kitchen
@@ -21,18 +19,16 @@ munch
 Pillow
 psutil
 pygit2 >= 0.26.0
-python-openid;python_version<="2.7"
-python3-openid;python_version>="3.0"
+python3-openid
 python-openid-cla
 python-openid-teams
 redis
 requests
+setuptools
 six
 # sqlalchemy minimum 0.8
 sqlalchemy >= 0.8
-# 1.4.0 is broken, 1.4.0-post-1 works but gives odd results on newer setuptools
-# the latest version 1.5.0 is also known to work
-straight.plugin
+straight.plugin >= 1.5.0
 whitenoise
 wtforms
 
diff --git a/setup.cfg b/setup.cfg
index b8d5112..8bfd5a1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,15 +1,3 @@
-[flake8]
-show-source = True
-max-line-length = 80
-exclude = .git,.tox,dist,*egg,build,tools
-application-import-names = toddlers
-import-order-style = google
-
-[tool:pytest]
-addopts = --cov-config .coveragerc --cov-report=term-missing --cov-report xml --cov-report html
-
-[tool:black]
-
 [egg_info]
 tag_build = 
 tag_date = 0
diff --git a/setup.py b/setup.py
index e9c3646..5f08efa 100644
--- a/setup.py
+++ b/setup.py
@@ -68,13 +68,13 @@ setup(
     classifiers=[
         "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)",
         "Operating System :: POSIX :: Linux",
-        "Programming Language :: Python :: 2",
-        "Programming Language :: Python :: 2.7",
         "Programming Language :: Python :: 3",
         "Programming Language :: Python :: 3.4",
         "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
         "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
         "Topic :: Software Development :: Bug Tracking",
         "Topic :: Software Development :: Version Control",
diff --git a/tests/__init__.py b/tests/__init__.py
index b6253a5..598ebf5 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -134,6 +134,8 @@ LOGGING = {
     # that applies to all log messages not handled by a different logger
     "root": {"level": "WARN", "handlers": ["console"]},
 }
+
+NOGITHOOKS = %(nogithooks)s
 """
 # The Celery docs warn against using task_always_eager:
 # http://docs.celeryproject.org/en/latest/userguide/testing.html
@@ -148,7 +150,7 @@ WAIT_REGEX = re.compile(r"""var _url = '(\/wait\/[a-z0-9-]+\??.*)'""")
 
 
 def get_wait_target(html):
-    """ This parses the window.location out of the HTML for the wait page. """
+    """This parses the window.location out of the HTML for the wait page."""
     found = WAIT_REGEX.findall(html)
     if len(found) == 0:
         raise Exception("Not able to get wait target in %s" % html)
@@ -156,7 +158,7 @@ def get_wait_target(html):
 
 
 def get_post_target(html):
-    """ This parses the wait page form to get the POST url. """
+    """This parses the wait page form to get the POST url."""
     soup = BeautifulSoup(html, "html.parser")
     form = soup.find(id="waitform")
     if not form:
@@ -165,7 +167,7 @@ def get_post_target(html):
 
 
 def get_post_args(html):
-    """ This parses the wait page for the hidden arguments of the form. """
+    """This parses the wait page for the hidden arguments of the form."""
     soup = BeautifulSoup(html, "html.parser")
     output = {}
     inputs = soup.find_all("input")
@@ -179,7 +181,7 @@ def get_post_args(html):
 
 def create_maybe_waiter(method, getter):
     def maybe_waiter(*args, **kwargs):
-        """ A wrapper for self.app.get()/.post() that will resolve wait's """
+        """A wrapper for self.app.get()/.post() that will resolve wait's"""
         result = method(*args, **kwargs)
 
         # Handle the POST wait case
@@ -221,7 +223,7 @@ def create_maybe_waiter(method, getter):
 
 @contextmanager
 def user_set(APP, user, keep_get_user=False):
-    """ Set the provided user as fas_user in the provided application."""
+    """Set the provided user as fas_user in the provided application."""
 
     # Hack used to remove the before_request function set by
     # flask.ext.fas_openid.FAS which otherwise kills our effort to set a
@@ -253,7 +255,7 @@ def user_set(APP, user, keep_get_user=False):
 
 
 def create_user(session, username, fullname, emails):
-    """ Create an user with the provided information.
+    """Create an user with the provided information.
     Note that `emails` should be a list of emails.
     """
     user = pagure.lib.model.User(
@@ -294,7 +296,7 @@ class SimplePagureTest(unittest.TestCase):
 
     @mock.patch("pagure.lib.notify.fedmsg_publish", mock.MagicMock())
     def __init__(self, method_name="runTest"):
-        """ Constructor. """
+        """Constructor."""
         unittest.TestCase.__init__(self, method_name)
         self.session = None
         self.path = None
@@ -303,7 +305,7 @@ class SimplePagureTest(unittest.TestCase):
         self.results = {}
 
     def perfMaxWalks(self, max_walks, max_steps):
-        """ Check that we have not performed too many walks/steps. """
+        """Check that we have not performed too many walks/steps."""
         num_walks = 0
         num_steps = 0
         for reqstat in perfrepo.REQUESTS:
@@ -324,7 +326,7 @@ class SimplePagureTest(unittest.TestCase):
         )
 
     def perfReset(self):
-        """ Reset perfrepo stats. """
+        """Reset perfrepo stats."""
         perfrepo.reset_stats()
         perfrepo.REQUESTS = []
 
@@ -333,7 +335,8 @@ class SimplePagureTest(unittest.TestCase):
         self.dbfolder = tempfile.mkdtemp(prefix="pagure-tests-")
         self.dbpath = "sqlite:///%s/db.sqlite" % self.dbfolder
         session = pagure.lib.model.create_tables(
-            self.dbpath, acls=pagure_config.get("ACLS", {}),
+            self.dbpath,
+            acls=pagure_config.get("ACLS", {}),
         )
         self.db_session = session
 
@@ -396,6 +399,7 @@ class SimplePagureTest(unittest.TestCase):
             "repospanner_admin_override": "False",
             "repospanner_new_fork": "True",
             "repospanner_admin_migration": "False",
+            "nogithooks": False,
         }
         config_values.update(self.config_values)
         self.config_values = config_values
@@ -484,7 +488,7 @@ class SimplePagureTest(unittest.TestCase):
         self.session.commit()
 
     def set_auth_status(self, value):
-        """ Set the return value for the test auth """
+        """Set the return value for the test auth"""
         with open(
             os.path.join(self.path, "testauth_status.json"), "w"
         ) as statusfile:
@@ -516,7 +520,7 @@ class SimplePagureTest(unittest.TestCase):
         return tuple(wtforms_v)
 
     def get_arrow_version(self):
-        """ Returns the arrow version as a tuple."""
+        """Returns the arrow version as a tuple."""
         import arrow
 
         arrow_v = arrow.__version__.split(".")
@@ -540,10 +544,10 @@ class SimplePagureTest(unittest.TestCase):
 
 
 class Modeltests(SimplePagureTest):
-    """ Model tests. """
+    """Model tests."""
 
     def setUp(self):  # pylint: disable=invalid-name
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         # Clean up test performance info
         super(Modeltests, self).setUp()
         self.app.get = create_maybe_waiter(self.app.get, self.app.get)
@@ -553,12 +557,12 @@ class Modeltests(SimplePagureTest):
         self.session = pagure.lib.query.create_session(self.dbpath)
 
     def tearDown(self):  # pylint: disable=invalid-name
-        """ Remove the test.db database if there is one. """
+        """Remove the test.db database if there is one."""
         self.broker_client.flushall()
         super(Modeltests, self).tearDown()
 
     def create_project_full(self, projectname, extra=None):
-        """ Create a project via the API.
+        """Create a project via the API.
 
         This makes sure that the repo is fully setup the way a normal new
         project would be, with hooks and all setup.
@@ -579,12 +583,12 @@ class Modeltests(SimplePagureTest):
 
 
 class FakeGroup(object):  # pylint: disable=too-few-public-methods
-    """ Fake object used to make the FakeUser object closer to the
+    """Fake object used to make the FakeUser object closer to the
     expectations.
     """
 
     def __init__(self, name):
-        """ Constructor.
+        """Constructor.
         :arg name: the name given to the name attribute of this object.
         """
         self.name = name
@@ -592,12 +596,12 @@ class FakeGroup(object):  # pylint: disable=too-few-public-methods
 
 
 class FakeUser(object):  # pylint: disable=too-few-public-methods
-    """ Fake user used to test the fedocallib library. """
+    """Fake user used to test the fedocallib library."""
 
     def __init__(
         self, groups=None, username="username", cla_done=True, id=None
     ):
-        """ Constructor.
+        """Constructor.
         :arg groups: list of the groups in which this fake user is
             supposed to be.
         """
@@ -633,7 +637,7 @@ def create_locks(session, project):
 
 
 def create_projects(session, is_fork=False, user_id=1, hook_token_suffix=""):
-    """ Create some projects in the database. """
+    """Create some projects in the database."""
     item = pagure.lib.model.Project(
         user_id=user_id,  # pingou
         name="test",
@@ -677,7 +681,7 @@ def create_projects(session, is_fork=False, user_id=1, hook_token_suffix=""):
 
 
 def create_projects_git(folder, bare=False):
-    """ Create some projects in the database. """
+    """Create some projects in the database."""
     repos = []
     for project in [
         "test.git",
@@ -693,7 +697,7 @@ def create_projects_git(folder, bare=False):
 
 
 def create_tokens(session, user_id=1, project_id=1, suffix=None):
-    """ Create some tokens for the project in the database. """
+    """Create some tokens for the project in the database."""
     token = "aaabbbcccddd"
     if suffix:
         token += suffix
@@ -730,7 +734,7 @@ def create_tokens(session, user_id=1, project_id=1, suffix=None):
 
 
 def create_tokens_acl(session, token_id="aaabbbcccddd", acl_name=None):
-    """ Create some ACLs for the token. If acl_name is not set, the token will
+    """Create some ACLs for the token. If acl_name is not set, the token will
     have all the ACLs enabled.
     """
     if acl_name is None:
@@ -750,7 +754,7 @@ def create_tokens_acl(session, token_id="aaabbbcccddd", acl_name=None):
 
 
 def _clone_and_top_commits(folder, branch, branch_ref=False):
-    """ Clone the repository, checkout the specified branch and return
+    """Clone the repository, checkout the specified branch and return
     the top commit of that branch if there is one.
     Returns the repo, the path to the clone and the top commit(s) in a tuple
     or the repo, the path to the clone and the reference to the branch
@@ -787,7 +791,7 @@ def _clone_and_top_commits(folder, branch, branch_ref=False):
 
 
 def add_content_git_repo(folder, branch="master", append=None):
-    """ Create some content for the specified git repo. """
+    """Create some content for the specified git repo."""
     repo, newfolder, parents = _clone_and_top_commits(folder, branch)
 
     # Create a file in that git repo
@@ -860,7 +864,7 @@ def add_content_git_repo(folder, branch="master", append=None):
 
 
 def add_readme_git_repo(folder, readme_name="README.rst", branch="master"):
-    """ Create a README file for the specified git repo. """
+    """Create a README file for the specified git repo."""
     repo, newfolder, parents = _clone_and_top_commits(folder, branch)
 
     if readme_name == "README.rst":
@@ -926,7 +930,7 @@ that should never get displayed on the website if there is a README.rst in the r
 def add_commit_git_repo(
     folder, ncommits=10, filename="sources", branch="master", symlink_to=None
 ):
-    """ Create some more commits for the specified git repo. """
+    """Create some more commits for the specified git repo."""
     repo, newfolder, branch_ref_obj = _clone_and_top_commits(
         folder, branch, branch_ref=True
     )
@@ -978,7 +982,7 @@ def add_commit_git_repo(
 
 
 def add_tag_git_repo(folder, tagname, obj_hash, message):
-    """ Add a tag to the given object of the given repo annotated by given message. """
+    """Add a tag to the given object of the given repo annotated by given message."""
     repo, newfolder, branch_ref_obj = _clone_and_top_commits(
         folder, "master", branch_ref=True
     )
@@ -1011,7 +1015,7 @@ def add_content_to_git(
     author=("Alice Author", "alice@authors.tld"),
     commiter=("Cecil Committer", "cecil@committers.tld"),
 ):
-    """ Create some more commits for the specified git repo. """
+    """Create some more commits for the specified git repo."""
     repo, newfolder, branch_ref_obj = _clone_and_top_commits(
         folder, branch, branch_ref=True
     )
@@ -1065,7 +1069,7 @@ def add_content_to_git(
 
 
 def add_binary_git_repo(folder, filename):
-    """ Create a fake image file for the specified git repo. """
+    """Create a fake image file for the specified git repo."""
     repo, newfolder, parents = _clone_and_top_commits(folder, "master")
 
     content = b"""\x00\x00\x01\x00\x01\x00\x18\x18\x00\x00\x01\x00 \x00\x88
@@ -1107,7 +1111,7 @@ def add_binary_git_repo(folder, filename):
 
 
 def remove_file_git_repo(folder, filename, branch="master"):
-    """ Delete the specified file on the give git repo and branch. """
+    """Delete the specified file on the give git repo and branch."""
     repo, newfolder, parents = _clone_and_top_commits(folder, branch)
 
     # Remove file
@@ -1146,10 +1150,11 @@ def add_pull_request_git_repo(
     branch_from="feature",
     user="pingou",
     allow_rebase=False,
+    append_content=None,
 ):
-    """ Set up the git repo and create the corresponding PullRequest
-        object.
-        """
+    """Set up the git repo and create the corresponding PullRequest
+    object.
+    """
 
     # Clone the main repo
     gitrepo = os.path.join(folder, "repos", repo.path)
@@ -1194,8 +1199,11 @@ def add_pull_request_git_repo(
     remote.fetch()
 
     # Edit the sources file again
+    content = "foo\n bar\nbaz\n boose"
+    if append_content:
+        content = content + append_content
     with open(os.path.join(new_gitrepo, "sources"), "w") as stream:
-        stream.write("foo\n bar\nbaz\n boose")
+        stream.write(content)
     clone_repo.index.add("sources")
     clone_repo.index.write()
 
@@ -1273,7 +1281,7 @@ def get_alerts(html):
 
 
 def definitely_wait(result):
-    """ Helper function for definitely waiting in _maybe_wait. """
+    """Helper function for definitely waiting in _maybe_wait."""
     result.wait()
 
 
diff --git a/tests/test_dev_data.py b/tests/test_dev_data.py
index 85d7e40..6aeab47 100644
--- a/tests/test_dev_data.py
+++ b/tests/test_dev_data.py
@@ -30,9 +30,7 @@ class TestDevData(tests.Modeltests):
     maxDiff = None
 
     def test_dev_data_all(self):
-        """Check how dev-data --all performs
-
-        """
+        """Check how dev-data --all performs"""
 
         config_path = os.path.join(self.path, "config")
         with open(config_path, "w") as f:
@@ -81,9 +79,7 @@ WARNING: Deleting all data from sqlite:///%s/db_dev_data.sqlite
         self.assertEqual(stdout, output)
 
     def test_dev_data_delete(self):
-        """Check how dev-data --init --delete performs
-
-        """
+        """Check how dev-data --init --delete performs"""
 
         config_path = os.path.join(self.path, "config")
 
@@ -114,9 +110,7 @@ WARNING: Deleting all data from %s
         self.assertEqual(stdout.split("\n"), output.split("\n"))
 
     def test_dev_data_init(self):
-        """Check how dev-data --init performs
-
-        """
+        """Check how dev-data --init performs"""
 
         config_path = os.path.join(self.path, "config")
 
diff --git a/tests/test_fnmatch.py b/tests/test_fnmatch.py
index 18e1eff..eceb3fc 100644
--- a/tests/test_fnmatch.py
+++ b/tests/test_fnmatch.py
@@ -25,7 +25,7 @@ class FnmatchTests(unittest.TestCase):
     """Tests for the streaming server."""
 
     def test_fnmatch(self):
-        """ Test the matching done by fnmatch. """
+        """Test the matching done by fnmatch."""
         matrix = [
             ["pagure", "*", True],
             ["ns/pagure", "*", True],
diff --git a/tests/test_pagure_admin.py b/tests/test_pagure_admin.py
index 3e6c20e..026ade0 100644
--- a/tests/test_pagure_admin.py
+++ b/tests/test_pagure_admin.py
@@ -35,18 +35,17 @@ import tests  # noqa
 
 
 class PagureAdminAdminTokenEmptytests(tests.Modeltests):
-    """ Tests for pagure-admin admin-token when there is nothing in the DB
-    """
+    """Tests for pagure-admin admin-token when there is nothing in the DB"""
 
     populate_db = False
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureAdminAdminTokenEmptytests, self).setUp()
         pagure.cli.admin.session = self.session
 
     def test_do_create_admin_token_no_user(self):
-        """ Test the do_create_admin_token function of pagure-admin without
+        """Test the do_create_admin_token function of pagure-admin without
         user.
         """
         exp_date = datetime.date.today() + datetime.timedelta(days=300)
@@ -61,7 +60,7 @@ class PagureAdminAdminTokenEmptytests(tests.Modeltests):
         self.assertEqual(cm.exception.args[0], 'No user "pingou" found')
 
     def test_do_list_admin_token_empty(self):
-        """ Test the do_list_admin_token function of pagure-admin when there
+        """Test the do_list_admin_token function of pagure-admin when there
         are not tokens in the db.
         """
         list_args = munch.Munch(
@@ -80,12 +79,12 @@ class PagureAdminAdminTokenEmptytests(tests.Modeltests):
 
 
 class PagureAdminAdminRefreshGitolitetests(tests.Modeltests):
-    """ Tests for pagure-admin refresh-gitolite """
+    """Tests for pagure-admin refresh-gitolite"""
 
     populate_db = False
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureAdminAdminRefreshGitolitetests, self).setUp()
         pagure.cli.admin.session = self.session
 
@@ -124,7 +123,7 @@ class PagureAdminAdminRefreshGitolitetests(tests.Modeltests):
     @patch("pagure.cli.admin._ask_confirmation")
     @patch("pagure.lib.git_auth.get_git_auth_helper")
     def test_do_refresh_gitolite_no_args(self, get_helper, conf):
-        """ Test the do_generate_acl function with no special args. """
+        """Test the do_generate_acl function with no special args."""
         conf.return_value = True
         helper = MagicMock()
         get_helper.return_value = helper
@@ -142,7 +141,7 @@ class PagureAdminAdminRefreshGitolitetests(tests.Modeltests):
     @patch("pagure.cli.admin._ask_confirmation")
     @patch("pagure.lib.git_auth.get_git_auth_helper")
     def test_do_refresh_gitolite_all_project(self, get_helper, conf):
-        """ Test the do_generate_acl function for all projects. """
+        """Test the do_generate_acl function for all projects."""
         conf.return_value = True
         helper = MagicMock()
         get_helper.return_value = helper
@@ -160,7 +159,7 @@ class PagureAdminAdminRefreshGitolitetests(tests.Modeltests):
     @patch("pagure.cli.admin._ask_confirmation")
     @patch("pagure.lib.git_auth.get_git_auth_helper")
     def test_do_refresh_gitolite_one_project(self, get_helper, conf):
-        """ Test the do_generate_acl function for a certain project. """
+        """Test the do_generate_acl function for a certain project."""
         conf.return_value = True
         helper = MagicMock()
         get_helper.return_value = helper
@@ -178,8 +177,7 @@ class PagureAdminAdminRefreshGitolitetests(tests.Modeltests):
     @patch("pagure.cli.admin._ask_confirmation")
     @patch("pagure.lib.git_auth.get_git_auth_helper")
     def test_do_refresh_gitolite_one_project_and_all(self, get_helper, conf):
-        """ Test the do_generate_acl function for a certain project and all.
-        """
+        """Test the do_generate_acl function for a certain project and all."""
         conf.return_value = True
         helper = MagicMock()
         get_helper.return_value = helper
@@ -197,7 +195,7 @@ class PagureAdminAdminRefreshGitolitetests(tests.Modeltests):
     @patch("pagure.cli.admin._ask_confirmation")
     @patch("pagure.lib.git_auth.get_git_auth_helper")
     def test_do_refresh_gitolite_one_group(self, get_helper, conf):
-        """ Test the do_generate_acl function for a certain group. """
+        """Test the do_generate_acl function for a certain group."""
         conf.return_value = True
         helper = MagicMock()
         get_helper.return_value = helper
@@ -214,12 +212,12 @@ class PagureAdminAdminRefreshGitolitetests(tests.Modeltests):
 
 
 class PagureAdminAdminTokentests(tests.Modeltests):
-    """ Tests for pagure-admin admin-token """
+    """Tests for pagure-admin admin-token"""
 
     populate_db = False
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureAdminAdminTokentests, self).setUp()
         pagure.cli.admin.session = self.session
 
@@ -241,7 +239,7 @@ class PagureAdminAdminTokentests(tests.Modeltests):
     @patch("pagure.cli.admin._get_input")
     @patch("pagure.cli.admin._ask_confirmation")
     def test_do_create_admin_token(self, conf, rinp):
-        """ Test the do_create_admin_token function of pagure-admin. """
+        """Test the do_create_admin_token function of pagure-admin."""
         conf.return_value = True
         rinp.return_value = "1,2,3"
 
@@ -274,7 +272,7 @@ class PagureAdminAdminTokentests(tests.Modeltests):
     @patch("pagure.cli.admin._get_input")
     @patch("pagure.cli.admin._ask_confirmation")
     def test_do_list_admin_token(self, conf, rinp):
-        """ Test the do_list_admin_token function of pagure-admin. """
+        """Test the do_list_admin_token function of pagure-admin."""
         # Create an admin token to use
         conf.return_value = True
         rinp.return_value = "1,2,3"
@@ -321,8 +319,8 @@ class PagureAdminAdminTokentests(tests.Modeltests):
         self.assertEqual(output, "No admin tokens found\n")
 
     def test_do_list_admin_token_non_admin_acls(self):
-        """ Test the do_list_admin_token function of pagure-admin for a token
-        without any admin ACL. """
+        """Test the do_list_admin_token function of pagure-admin for a token
+        without any admin ACL."""
         exp_date = datetime.date.today() + datetime.timedelta(days=300)
         pagure.lib.query.add_token_to_user(
             self.session,
@@ -367,7 +365,7 @@ class PagureAdminAdminTokentests(tests.Modeltests):
     @patch("pagure.cli.admin._get_input")
     @patch("pagure.cli.admin._ask_confirmation")
     def test_do_info_admin_token(self, conf, rinp):
-        """ Test the do_info_admin_token function of pagure-admin. """
+        """Test the do_info_admin_token function of pagure-admin."""
         # Create an admin token to use
         conf.return_value = True
         rinp.return_value = "2,4,5"
@@ -415,8 +413,8 @@ class PagureAdminAdminTokentests(tests.Modeltests):
         )
 
     def test_do_info_admin_token_non_admin_acl(self):
-        """ Test the do_info_admin_token function of pagure-admin for a
-        token not having any admin ACL. """
+        """Test the do_info_admin_token function of pagure-admin for a
+        token not having any admin ACL."""
         exp_date = datetime.date.today() + datetime.timedelta(days=300)
         pagure.lib.query.add_token_to_user(
             self.session,
@@ -461,7 +459,7 @@ class PagureAdminAdminTokentests(tests.Modeltests):
     @patch("pagure.cli.admin._get_input")
     @patch("pagure.cli.admin._ask_confirmation")
     def test_do_expire_admin_token(self, conf, rinp):
-        """ Test the do_expire_admin_token function of pagure-admin. """
+        """Test the do_expire_admin_token function of pagure-admin."""
         # Create an admin token to use
         conf.return_value = True
         rinp.return_value = "1,2,3"
@@ -533,8 +531,8 @@ class PagureAdminAdminTokentests(tests.Modeltests):
     @patch("pagure.cli.admin._get_input")
     @patch("pagure.cli.admin._ask_confirmation")
     def test_do_expire_admin_token_non_admin_acls(self, conf, rinp):
-        """ Test the do_expire_admin_token function of pagure-admin for a token
-        without any admin ACL. """
+        """Test the do_expire_admin_token function of pagure-admin for a token
+        without any admin ACL."""
 
         # Create an admin token to use
         conf.return_value = True
@@ -590,8 +588,8 @@ class PagureAdminAdminTokentests(tests.Modeltests):
     @patch("pagure.cli.admin._get_input")
     @patch("pagure.cli.admin._ask_confirmation")
     def test_do_update_admin_token_invalid_date(self, conf, rinp):
-        """ Test the do_update_admin_token function of pagure-admin with
-        an invalid date. """
+        """Test the do_update_admin_token function of pagure-admin with
+        an invalid date."""
 
         # Create an admin token to use
         conf.return_value = True
@@ -637,8 +635,8 @@ class PagureAdminAdminTokentests(tests.Modeltests):
     @patch("pagure.cli.admin._get_input")
     @patch("pagure.cli.admin._ask_confirmation")
     def test_do_update_admin_token_invalid_date2(self, conf, rinp):
-        """ Test the do_update_admin_token function of pagure-admin with
-        an invalid date. """
+        """Test the do_update_admin_token function of pagure-admin with
+        an invalid date."""
 
         # Create an admin token to use
         conf.return_value = True
@@ -686,8 +684,8 @@ class PagureAdminAdminTokentests(tests.Modeltests):
     @patch("pagure.cli.admin._get_input")
     @patch("pagure.cli.admin._ask_confirmation")
     def test_do_update_admin_token_invalid_date3(self, conf, rinp):
-        """ Test the do_update_admin_token function of pagure-admin with
-        an invalid date (is today). """
+        """Test the do_update_admin_token function of pagure-admin with
+        an invalid date (is today)."""
 
         # Create an admin token to use
         conf.return_value = True
@@ -739,7 +737,7 @@ class PagureAdminAdminTokentests(tests.Modeltests):
     @patch("pagure.cli.admin._get_input")
     @patch("pagure.cli.admin._ask_confirmation")
     def test_do_update_admin_token(self, conf, rinp):
-        """ Test the do_update_admin_token function of pagure-admin. """
+        """Test the do_update_admin_token function of pagure-admin."""
 
         # Create an admin token to use
         conf.return_value = True
@@ -826,8 +824,8 @@ class PagureAdminAdminTokentests(tests.Modeltests):
     @patch("pagure.cli.admin._get_input")
     @patch("pagure.cli.admin._ask_confirmation")
     def test_do_update_admin_token_non_admin_acls(self, conf, rinp):
-        """ Test the do_update_admin_token function of pagure-admin for a token
-        without any admin ACL. """
+        """Test the do_update_admin_token function of pagure-admin for a token
+        without any admin ACL."""
 
         # Create an admin token to use
         conf.return_value = True
@@ -895,12 +893,12 @@ class PagureAdminAdminTokentests(tests.Modeltests):
 
 
 class PagureAdminGetWatchTests(tests.Modeltests):
-    """ Tests for pagure-admin get-watch """
+    """Tests for pagure-admin get-watch"""
 
     populate_db = False
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureAdminGetWatchTests, self).setUp()
         pagure.cli.admin.session = self.session
 
@@ -949,7 +947,7 @@ class PagureAdminGetWatchTests(tests.Modeltests):
         pagure.cli.admin.session = self.session
 
     def test_get_watch_get_project_unknown_project(self):
-        """ Test the get-watch function of pagure-admin with an unknown
+        """Test the get-watch function of pagure-admin with an unknown
         project.
         """
         args = munch.Munch({"project": "foobar", "user": "pingou"})
@@ -960,7 +958,7 @@ class PagureAdminGetWatchTests(tests.Modeltests):
         )
 
     def test_get_watch_get_project_invalid_project(self):
-        """ Test the get-watch function of pagure-admin with an invalid
+        """Test the get-watch function of pagure-admin with an invalid
         project.
         """
         args = munch.Munch({"project": "fo/o/bar", "user": "pingou"})
@@ -972,16 +970,14 @@ class PagureAdminGetWatchTests(tests.Modeltests):
         )
 
     def test_get_watch_get_project_invalid_user(self):
-        """ Test the get-watch function of pagure-admin on a invalid user.
-        """
+        """Test the get-watch function of pagure-admin on a invalid user."""
         args = munch.Munch({"project": "test", "user": "beebop"})
         with self.assertRaises(pagure.exceptions.PagureException) as cm:
             pagure.cli.admin.do_get_watch_status(args)
         self.assertEqual(cm.exception.args[0], 'No user "beebop" found')
 
     def test_get_watch_get_project(self):
-        """ Test the get-watch function of pagure-admin on a regular project.
-        """
+        """Test the get-watch function of pagure-admin on a regular project."""
         args = munch.Munch({"project": "test", "user": "pingou"})
         with tests.capture_output() as output:
             pagure.cli.admin.do_get_watch_status(args)
@@ -993,8 +989,7 @@ class PagureAdminGetWatchTests(tests.Modeltests):
         )
 
     def test_get_watch_get_project_not_watching(self):
-        """ Test the get-watch function of pagure-admin on a regular project.
-        """
+        """Test the get-watch function of pagure-admin on a regular project."""
 
         args = munch.Munch({"project": "test", "user": "foo"})
         with tests.capture_output() as output:
@@ -1005,8 +1000,7 @@ class PagureAdminGetWatchTests(tests.Modeltests):
         )
 
     def test_get_watch_get_project_namespaced(self):
-        """ Test the get-watch function of pagure-admin on a namespaced project.
-        """
+        """Test the get-watch function of pagure-admin on a namespaced project."""
 
         args = munch.Munch({"project": "somenamespace/test", "user": "pingou"})
         with tests.capture_output() as output:
@@ -1019,8 +1013,7 @@ class PagureAdminGetWatchTests(tests.Modeltests):
         )
 
     def test_get_watch_get_project_namespaced_not_watching(self):
-        """ Test the get-watch function of pagure-admin on a namespaced project.
-        """
+        """Test the get-watch function of pagure-admin on a namespaced project."""
 
         args = munch.Munch({"project": "somenamespace/test", "user": "foo"})
         with tests.capture_output() as output:
@@ -1036,12 +1029,12 @@ class PagureAdminGetWatchTests(tests.Modeltests):
 
 
 class PagureAdminUpdateWatchTests(tests.Modeltests):
-    """ Tests for pagure-admin update-watch """
+    """Tests for pagure-admin update-watch"""
 
     populate_db = False
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureAdminUpdateWatchTests, self).setUp()
         pagure.cli.admin.session = self.session
 
@@ -1090,7 +1083,7 @@ class PagureAdminUpdateWatchTests(tests.Modeltests):
         pagure.cli.admin.session = self.session
 
     def test_get_watch_update_project_unknown_project(self):
-        """ Test the update-watch function of pagure-admin on an unknown
+        """Test the update-watch function of pagure-admin on an unknown
         project.
         """
         args = munch.Munch(
@@ -1103,7 +1096,7 @@ class PagureAdminUpdateWatchTests(tests.Modeltests):
         )
 
     def test_get_watch_update_project_invalid_project(self):
-        """ Test the update-watch function of pagure-admin on an invalid
+        """Test the update-watch function of pagure-admin on an invalid
         project.
         """
         args = munch.Munch(
@@ -1117,15 +1110,14 @@ class PagureAdminUpdateWatchTests(tests.Modeltests):
         )
 
     def test_get_watch_update_project_invalid_user(self):
-        """ Test the update-watch function of pagure-admin on an invalid user.
-        """
+        """Test the update-watch function of pagure-admin on an invalid user."""
         args = munch.Munch({"project": "test", "user": "foob", "status": "1"})
         with self.assertRaises(pagure.exceptions.PagureException) as cm:
             pagure.cli.admin.do_update_watch_status(args)
         self.assertEqual(cm.exception.args[0], 'No user "foob" found')
 
     def test_get_watch_update_project_invalid_status(self):
-        """ Test the update-watch function of pagure-admin with an invalid
+        """Test the update-watch function of pagure-admin with an invalid
         status.
         """
         args = munch.Munch(
@@ -1139,7 +1131,7 @@ class PagureAdminUpdateWatchTests(tests.Modeltests):
         )
 
     def test_get_watch_update_project_no_effect(self):
-        """ Test the update-watch function of pagure-admin with a regular
+        """Test the update-watch function of pagure-admin with a regular
         project - nothing changed.
         """
 
@@ -1177,12 +1169,12 @@ class PagureAdminUpdateWatchTests(tests.Modeltests):
 
 
 class PagureAdminReadOnlyTests(tests.Modeltests):
-    """ Tests for pagure-admin read-only """
+    """Tests for pagure-admin read-only"""
 
     populate_db = False
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureAdminReadOnlyTests, self).setUp()
         pagure.cli.admin.session = self.session
 
@@ -1222,7 +1214,7 @@ class PagureAdminReadOnlyTests(tests.Modeltests):
         pagure.cli.admin.session = self.session
 
     def test_read_only_unknown_project(self):
-        """ Test the read-only function of pagure-admin on an unknown
+        """Test the read-only function of pagure-admin on an unknown
         project.
         """
 
@@ -1234,7 +1226,7 @@ class PagureAdminReadOnlyTests(tests.Modeltests):
         )
 
     def test_read_only_invalid_project(self):
-        """ Test the read-only function of pagure-admin on an invalid
+        """Test the read-only function of pagure-admin on an invalid
         project.
         """
 
@@ -1247,7 +1239,7 @@ class PagureAdminReadOnlyTests(tests.Modeltests):
         )
 
     def test_read_only(self):
-        """ Test the read-only function of pagure-admin to get status of
+        """Test the read-only function of pagure-admin to get status of
         a non-namespaced project.
         """
 
@@ -1261,7 +1253,7 @@ class PagureAdminReadOnlyTests(tests.Modeltests):
         )
 
     def test_read_only_namespace(self):
-        """ Test the read-only function of pagure-admin to get status of
+        """Test the read-only function of pagure-admin to get status of
         a namespaced project.
         """
 
@@ -1278,7 +1270,7 @@ class PagureAdminReadOnlyTests(tests.Modeltests):
         )
 
     def test_read_only_namespace_changed(self):
-        """ Test the read-only function of pagure-admin to set the status of
+        """Test the read-only function of pagure-admin to set the status of
         a namespaced project.
         """
 
@@ -1321,7 +1313,7 @@ class PagureAdminReadOnlyTests(tests.Modeltests):
         )
 
     def test_read_only_no_change(self):
-        """ Test the read-only function of pagure-admin to set the status of
+        """Test the read-only function of pagure-admin to set the status of
         a namespaced project.
         """
 
@@ -1358,12 +1350,12 @@ class PagureAdminReadOnlyTests(tests.Modeltests):
 
 
 class PagureNewGroupTests(tests.Modeltests):
-    """ Tests for pagure-admin new-group """
+    """Tests for pagure-admin new-group"""
 
     populate_db = False
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureNewGroupTests, self).setUp()
         pagure.cli.admin.session = self.session
 
@@ -1387,7 +1379,7 @@ class PagureNewGroupTests(tests.Modeltests):
         self.assertEqual(len(groups), 0)
 
     def test_missing_display_name(self):
-        """ Test the new-group function of pagure-admin when the display name
+        """Test the new-group function of pagure-admin when the display name
         is missing from the args.
         """
 
@@ -1410,7 +1402,7 @@ class PagureNewGroupTests(tests.Modeltests):
         self.assertEqual(len(groups), 0)
 
     def test_missing_username(self):
-        """ Test the new-group function of pagure-admin when the username
+        """Test the new-group function of pagure-admin when the username
         is missing from the args.
         """
 
@@ -1435,7 +1427,7 @@ class PagureNewGroupTests(tests.Modeltests):
         self.assertEqual(len(groups), 0)
 
     def test_new_group(self):
-        """ Test the new-group function of pagure-admin when all arguments
+        """Test the new-group function of pagure-admin when all arguments
         are provided.
         """
 
@@ -1456,7 +1448,7 @@ class PagureNewGroupTests(tests.Modeltests):
     @patch.dict("pagure.config.config", {"ENABLE_GROUP_MNGT": False})
     @patch("pagure.cli.admin._ask_confirmation")
     def test_new_group_grp_mngt_off_no(self, conf):
-        """ Test the new-group function of pagure-admin when all arguments
+        """Test the new-group function of pagure-admin when all arguments
         are provided and ENABLE_GROUP_MNGT if off in the config and the user
         replies no to the question.
         """
@@ -1479,7 +1471,7 @@ class PagureNewGroupTests(tests.Modeltests):
     @patch.dict("pagure.config.config", {"ENABLE_GROUP_MNGT": False})
     @patch("pagure.cli.admin._ask_confirmation")
     def test_new_group_grp_mngt_off_yes(self, conf):
-        """ Test the new-group function of pagure-admin when all arguments
+        """Test the new-group function of pagure-admin when all arguments
         are provided and ENABLE_GROUP_MNGT if off in the config and the user
         replies yes to the question.
         """
@@ -1501,7 +1493,7 @@ class PagureNewGroupTests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"BLACKLISTED_GROUPS": ["foob"]})
     def test_new_group_grp_mngt_off_yes(self):
-        """ Test the new-group function of pagure-admin when all arguments
+        """Test the new-group function of pagure-admin when all arguments
         are provided but the group is black listed.
         """
 
@@ -1527,12 +1519,12 @@ class PagureNewGroupTests(tests.Modeltests):
 
 
 class PagureListGroupEmptyTests(tests.Modeltests):
-    """ Tests for pagure-admin list-groups """
+    """Tests for pagure-admin list-groups"""
 
     populate_db = False
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureListGroupEmptyTests, self).setUp()
         pagure.cli.admin.session = self.session
 
@@ -1557,7 +1549,7 @@ class PagureListGroupEmptyTests(tests.Modeltests):
 
     @patch("sys.stdout", new_callable=StringIO)
     def test_no_groups(self, mock_stdout):
-        """ Test the list-groups function of pagure-admin when there are no
+        """Test the list-groups function of pagure-admin when there are no
         groups in the database
         """
 
@@ -1574,12 +1566,12 @@ class PagureListGroupEmptyTests(tests.Modeltests):
 
 
 class PagureListGroupTests(tests.Modeltests):
-    """ Tests for pagure-admin list-groups """
+    """Tests for pagure-admin list-groups"""
 
     populate_db = False
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureListGroupTests, self).setUp()
         pagure.cli.admin.session = self.session
 
@@ -1616,7 +1608,7 @@ class PagureListGroupTests(tests.Modeltests):
 
     @patch("sys.stdout", new_callable=StringIO)
     def test_list_groups(self, mock_stdout):
-        """ Test the list-groups function of pagure-admin when there is one
+        """Test the list-groups function of pagure-admin when there is one
         group in the database
         """
 
@@ -1633,12 +1625,12 @@ class PagureListGroupTests(tests.Modeltests):
 
 
 class PagureBlockUserTests(tests.Modeltests):
-    """ Tests for pagure-admin block-user """
+    """Tests for pagure-admin block-user"""
 
     populate_db = False
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureBlockUserTests, self).setUp()
         pagure.cli.admin.session = self.session
 
@@ -1662,7 +1654,7 @@ class PagureBlockUserTests(tests.Modeltests):
         self.assertIsNone(user.refuse_sessions_before)
 
     def test_missing_date(self):
-        """ Test the block-user function of pagure-admin when the no date is
+        """Test the block-user function of pagure-admin when the no date is
         provided.
         """
 
@@ -1678,7 +1670,7 @@ class PagureBlockUserTests(tests.Modeltests):
         self.assertIsNone(user.refuse_sessions_before)
 
     def test_missing_username(self):
-        """ Test the block-user function of pagure-admin when the username
+        """Test the block-user function of pagure-admin when the username
         is missing from the args.
         """
 
@@ -1695,7 +1687,7 @@ class PagureBlockUserTests(tests.Modeltests):
         self.assertIsNone(user.refuse_sessions_before)
 
     def test_invalid_username(self):
-        """ Test the block-user function of pagure-admin when the username
+        """Test the block-user function of pagure-admin when the username
         provided does correspond to any user in the DB.
         """
 
@@ -1712,7 +1704,7 @@ class PagureBlockUserTests(tests.Modeltests):
         self.assertIsNone(user.refuse_sessions_before)
 
     def test_invalide_date(self):
-        """ Test the block-user function of pagure-admin when the provided
+        """Test the block-user function of pagure-admin when the provided
         date is incorrect.
         """
 
@@ -1733,7 +1725,7 @@ class PagureBlockUserTests(tests.Modeltests):
 
     @patch("pagure.cli.admin._ask_confirmation", MagicMock(return_value=True))
     def test_block_user(self):
-        """ Test the block-user function of pagure-admin when all arguments
+        """Test the block-user function of pagure-admin when all arguments
         are provided correctly.
         """
 
@@ -1747,7 +1739,7 @@ class PagureBlockUserTests(tests.Modeltests):
         self.assertIsNotNone(user.refuse_sessions_before)
 
     def test_list_blocked_user(self):
-        """ Test the block-user function of pagure-admin when all arguments
+        """Test the block-user function of pagure-admin when all arguments
         are provided correctly.
         """
 
@@ -1761,7 +1753,7 @@ class PagureBlockUserTests(tests.Modeltests):
 
     @patch("pagure.cli.admin._ask_confirmation", MagicMock(return_value=True))
     def test_list_blocked_user_with_data(self):
-        """ Test the block-user function of pagure-admin when all arguments
+        """Test the block-user function of pagure-admin when all arguments
         are provided correctly.
         """
         args = munch.Munch(
@@ -1783,7 +1775,7 @@ class PagureBlockUserTests(tests.Modeltests):
 
     @patch("pagure.cli.admin._ask_confirmation", MagicMock(return_value=True))
     def test_list_blocked_user_with_username_data(self):
-        """ Test the block-user function of pagure-admin when all arguments
+        """Test the block-user function of pagure-admin when all arguments
         are provided correctly.
         """
         args = munch.Munch(
@@ -1812,7 +1804,7 @@ class PagureBlockUserTests(tests.Modeltests):
 
     @patch("pagure.cli.admin._ask_confirmation", MagicMock(return_value=True))
     def test_list_blocked_user_with_date(self):
-        """ Test the block-user function of pagure-admin when all arguments
+        """Test the block-user function of pagure-admin when all arguments
         are provided correctly.
         """
         args = munch.Munch(
@@ -1827,7 +1819,7 @@ class PagureBlockUserTests(tests.Modeltests):
 
     @patch("pagure.cli.admin._ask_confirmation", MagicMock(return_value=True))
     def test_list_blocked_user_with_date_and_data(self):
-        """ Test the block-user function of pagure-admin when all arguments
+        """Test the block-user function of pagure-admin when all arguments
         are provided correctly.
         """
         args = munch.Munch(
@@ -1861,12 +1853,12 @@ class PagureBlockUserTests(tests.Modeltests):
 
 
 class PagureAdminDeleteProjectTests(tests.Modeltests):
-    """ Tests for pagure-admin delete-project """
+    """Tests for pagure-admin delete-project"""
 
     populate_db = False
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureAdminDeleteProjectTests, self).setUp()
         pagure.cli.admin.session = self.session
 
@@ -1906,7 +1898,7 @@ class PagureAdminDeleteProjectTests(tests.Modeltests):
         pagure.cli.admin.session = self.session
 
     def test_delete_project_unknown_project(self):
-        """ Test the read-only function of pagure-admin on an unknown
+        """Test the read-only function of pagure-admin on an unknown
         project.
         """
 
@@ -1921,7 +1913,7 @@ class PagureAdminDeleteProjectTests(tests.Modeltests):
         )
 
     def test_delete_project_invalid_project(self):
-        """ Test the read-only function of pagure-admin on an invalid
+        """Test the read-only function of pagure-admin on an invalid
         project.
         """
 
@@ -1937,7 +1929,7 @@ class PagureAdminDeleteProjectTests(tests.Modeltests):
 
     @patch("pagure.cli.admin._ask_confirmation", MagicMock(return_value=True))
     def test_delete_project(self):
-        """ Test the read-only function of pagure-admin to get status of
+        """Test the read-only function of pagure-admin to get status of
         a non-namespaced project.
         """
 
@@ -1956,7 +1948,7 @@ class PagureAdminDeleteProjectTests(tests.Modeltests):
 
     @patch("pagure.cli.admin._ask_confirmation", MagicMock(return_value=True))
     def test_delete_project_namespace(self):
-        """ Test the read-only function of pagure-admin to get status of
+        """Test the read-only function of pagure-admin to get status of
         a namespaced project.
         """
 
@@ -1979,7 +1971,7 @@ class PagureAdminDeleteProjectTests(tests.Modeltests):
 
     @patch("pagure.cli.admin._ask_confirmation", MagicMock(return_value=True))
     def test_delete_project_namespace_changed(self):
-        """ Test the read-only function of pagure-admin to set the status of
+        """Test the read-only function of pagure-admin to set the status of
         a namespaced project.
         """
 
@@ -2010,12 +2002,12 @@ class PagureAdminDeleteProjectTests(tests.Modeltests):
 
 
 class PagureCreateBranchTests(tests.Modeltests):
-    """ Tests for pagure-admin create-branch """
+    """Tests for pagure-admin create-branch"""
 
     populate_db = True
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureCreateBranchTests, self).setUp()
 
         # Create a couple of projects
@@ -2027,7 +2019,7 @@ class PagureCreateBranchTests(tests.Modeltests):
         pagure.cli.admin.session = self.session
 
     def test_create_branch_unknown_project(self):
-        """ Test the read-only function of pagure-admin on an unknown
+        """Test the read-only function of pagure-admin on an unknown
         project.
         """
 
@@ -2049,7 +2041,7 @@ class PagureCreateBranchTests(tests.Modeltests):
         )
 
     def test_create_branch_invalid_project(self):
-        """ Test the read-only function of pagure-admin on an invalid
+        """Test the read-only function of pagure-admin on an invalid
         project.
         """
 
@@ -2071,7 +2063,7 @@ class PagureCreateBranchTests(tests.Modeltests):
         )
 
     def test_create_branch_commit_and_branch_from(self):
-        """ Test the read-only function of pagure-admin to get status of
+        """Test the read-only function of pagure-admin to get status of
         a non-namespaced project.
         """
 
@@ -2094,7 +2086,7 @@ class PagureCreateBranchTests(tests.Modeltests):
         )
 
     def test_create_branch_no_branch_from(self):
-        """ Test the read-only function of pagure-admin to get status of
+        """Test the read-only function of pagure-admin to get status of
         a non-namespaced project.
         """
 
@@ -2115,7 +2107,7 @@ class PagureCreateBranchTests(tests.Modeltests):
         )
 
     def test_create_branch_no_commit_from(self):
-        """ Test the read-only function of pagure-admin to get status of
+        """Test the read-only function of pagure-admin to get status of
         a non-namespaced project.
         """
 
@@ -2136,7 +2128,7 @@ class PagureCreateBranchTests(tests.Modeltests):
         )
 
     def test_create_branch_from_branch(self):
-        """ Test the do_create_admin_token function of pagure-admin. """
+        """Test the do_create_admin_token function of pagure-admin."""
 
         gitrepo_path = os.path.join(self.path, "repos", "test.git")
         tests.add_content_git_repo(gitrepo_path)
@@ -2169,12 +2161,12 @@ class PagureCreateBranchTests(tests.Modeltests):
 
 
 class PagureSetDefaultBranchTests(tests.Modeltests):
-    """ Tests for pagure-admin set-default-branch """
+    """Tests for pagure-admin set-default-branch"""
 
     populate_db = True
 
     def setUp(self):
-        """ Set up the environment, run before every tests. """
+        """Set up the environment, run before every tests."""
         super(PagureSetDefaultBranchTests, self).setUp()
 
         # Create a couple of projects
@@ -2186,7 +2178,7 @@ class PagureSetDefaultBranchTests(tests.Modeltests):
         pagure.cli.admin.session = self.session
 
     def test_set_default_branch_unknown_project(self):
-        """ Test the set-default-branch function of pagure-admin on an unknown
+        """Test the set-default-branch function of pagure-admin on an unknown
         project.
         """
 
@@ -2201,7 +2193,7 @@ class PagureSetDefaultBranchTests(tests.Modeltests):
         )
 
     def test_set_default_branch_invalid_project(self):
-        """ Test the set-default-branch function of pagure-admin on an invalid
+        """Test the set-default-branch function of pagure-admin on an invalid
         project.
         """
 
@@ -2216,7 +2208,7 @@ class PagureSetDefaultBranchTests(tests.Modeltests):
         )
 
     def test_set_default_branch_unknown_branch(self):
-        """ Test the set-default-branch function of pagure-admin on an unknown
+        """Test the set-default-branch function of pagure-admin on an unknown
         branch.
         """
 
@@ -2228,8 +2220,7 @@ class PagureSetDefaultBranchTests(tests.Modeltests):
         )
 
     def test_set_default_branch_invalid_branch(self):
-        """ Test the set-default-branch function of pagure-admin on an invalid branch.
-        """
+        """Test the set-default-branch function of pagure-admin on an invalid branch."""
 
         args = munch.Munch(
             {"project": "test", "user": None, "branch": "~invalid~"}
@@ -2241,7 +2232,7 @@ class PagureSetDefaultBranchTests(tests.Modeltests):
         )
 
     def test_set_default_branch(self):
-        """ Test the set-default-branch funcion of pagure-admin. """
+        """Test the set-default-branch funcion of pagure-admin."""
 
         gitrepo_path = os.path.join(self.path, "repos", "test.git")
         tests.add_content_git_repo(gitrepo_path)
@@ -2262,5 +2253,121 @@ class PagureSetDefaultBranchTests(tests.Modeltests):
         self.assertEqual(gitrepo.head.shorthand, "dev")
 
 
+class PagureAdminUpdateAclsTests(tests.Modeltests):
+    """Tests for pagure-admin update-acls"""
+
+    populate_db = False
+    maxDiff = None
+
+    def setUp(self):
+        """Set up the environnment, ran before every tests."""
+        super(PagureAdminUpdateAclsTests, self).setUp()
+        pagure.cli.admin.session = self.session
+
+        # Make the imported pagure use the correct db session
+        pagure.cli.admin.session = self.session
+
+    def test_update_acls(self):
+        """Test the update-acls function of pagure-admin."""
+        args = munch.Munch()
+        with tests.capture_output() as output:
+            pagure.cli.admin.do_update_acls(args)
+
+        output = output.getvalue()
+        self.assertEqual(
+            "ACLS in the database synced with the list in the configuration file\n",
+            output,
+        )
+
+        acls = pagure.lib.query.get_acls(self.session)
+        self.assertEqual(
+            [a.name for a in acls],
+            [
+                "commit",
+                "commit_flag",
+                "create_branch",
+                "create_git_alias",
+                "create_project",
+                "delete_git_alias",
+                "fork_project",
+                "generate_acls_project",
+                "internal_access",
+                "issue_assign",
+                "issue_change_status",
+                "issue_comment",
+                "issue_create",
+                "issue_subscribe",
+                "issue_update",
+                "issue_update_custom_fields",
+                "issue_update_milestone",
+                "modify_git_alias",
+                "modify_project",
+                "pull_request_assign",
+                "pull_request_close",
+                "pull_request_comment",
+                "pull_request_create",
+                "pull_request_flag",
+                "pull_request_merge",
+                "pull_request_rebase",
+                "pull_request_subscribe",
+                "pull_request_update",
+                "tag_project",
+                "update_watch_status",
+            ],
+        )
+
+    @patch.dict(
+        "pagure.cli.admin._config",
+        {"ACLS": {"dummy_acls": "Dummy ACL for testing"}},
+    )
+    def test_update_acls_new_acls(self):
+        args = munch.Munch({"debug": True})
+        with tests.capture_output() as output:
+            pagure.cli.admin.do_update_acls(args)
+
+        output = output.getvalue()
+        self.assertEqual(
+            "ACLS in the database synced with the list in the configuration file\n",
+            output,
+        )
+        acls = pagure.lib.query.get_acls(self.session)
+        self.assertEqual(
+            [a.name for a in acls],
+            [
+                "commit",
+                "commit_flag",
+                "create_branch",
+                "create_git_alias",
+                "create_project",
+                "delete_git_alias",
+                "dummy_acls",
+                "fork_project",
+                "generate_acls_project",
+                "internal_access",
+                "issue_assign",
+                "issue_change_status",
+                "issue_comment",
+                "issue_create",
+                "issue_subscribe",
+                "issue_update",
+                "issue_update_custom_fields",
+                "issue_update_milestone",
+                "modify_git_alias",
+                "modify_project",
+                "pull_request_assign",
+                "pull_request_close",
+                "pull_request_comment",
+                "pull_request_create",
+                "pull_request_flag",
+                "pull_request_merge",
+                "pull_request_rebase",
+                "pull_request_subscribe",
+                "pull_request_update",
+                "tag_project",
+                "update_watch_status",
+            ],
+        )
+
+
 if __name__ == "__main__":
     unittest.main(verbosity=2)
diff --git a/tests/test_pagure_exclude_group_index.py b/tests/test_pagure_exclude_group_index.py
index aca3dd6..4fd683c 100644
--- a/tests/test_pagure_exclude_group_index.py
+++ b/tests/test_pagure_exclude_group_index.py
@@ -27,10 +27,10 @@ import tests
 
 
 class PagureExcludeGroupIndex(tests.Modeltests):
-    """ Tests the EXCLUDE_GROUP_INDEX configuration key in pagure """
+    """Tests the EXCLUDE_GROUP_INDEX configuration key in pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureExcludeGroupIndex, self).setUp()
 
         tests.create_projects(self.session)
@@ -64,7 +64,7 @@ class PagureExcludeGroupIndex(tests.Modeltests):
         self.assertEqual(msg, "Group added")
 
     def test_defaults_pingou(self):
-        """ Test which repo pingou has by default. """
+        """Test which repo pingou has by default."""
 
         repos = pagure.lib.query.search_projects(
             self.session, username="pingou", fork=False
@@ -75,7 +75,7 @@ class PagureExcludeGroupIndex(tests.Modeltests):
             self.assertEqual(repos[idx].name, name)
 
     def test_defaults_foo(self):
-        """ Test which repo foo has by default. """
+        """Test which repo foo has by default."""
 
         repos = pagure.lib.query.search_projects(
             self.session, username="foo", fork=False
@@ -84,7 +84,7 @@ class PagureExcludeGroupIndex(tests.Modeltests):
         self.assertEqual(len(repos), 0)
 
     def test_add_foo_test(self):
-        """ Test adding foo to the test project. """
+        """Test adding foo to the test project."""
 
         group = pagure.lib.query.search_groups(
             self.session, group_name="provenpackager"
@@ -119,7 +119,7 @@ class PagureExcludeGroupIndex(tests.Modeltests):
         self.assertEqual(repos[0].name, "test2")
 
     def test_excluding_provenpackager(self):
-        """ Test retrieving user's repo with a group excluded. """
+        """Test retrieving user's repo with a group excluded."""
 
         # Add `foo` to `provenpackager`
         group = pagure.lib.query.search_groups(
diff --git a/tests/test_pagure_flask.py b/tests/test_pagure_flask.py
index 82b5dc0..6c21678 100644
--- a/tests/test_pagure_flask.py
+++ b/tests/test_pagure_flask.py
@@ -30,10 +30,10 @@ import tests
 
 
 class PagureGetRemoteRepoPath(tests.SimplePagureTest):
-    """ Tests for pagure """
+    """Tests for pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureGetRemoteRepoPath, self).setUp()
 
         tests.create_projects(self.session)
@@ -47,7 +47,7 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
         mock.MagicMock(side_effect=pygit2.GitError),
     )
     def test_passing(self):
-        """ Test get_remote_repo_path in pagure. """
+        """Test get_remote_repo_path in pagure."""
         output = pagure.utils.get_remote_repo_path(
             os.path.join(self.path, "repos", "test2.git"),
             "master",
@@ -57,16 +57,15 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
         self.assertTrue(output.endswith("repos_test2.git_master"))
 
     def test_is_repo_committer_logged_out(self):
-        """ Test is_repo_committer in pagure when there is no logged in user.
-        """
+        """Test is_repo_committer in pagure when there is no logged in user."""
         repo = pagure.lib.query._get_project(self.session, "test")
         with self.app.application.app_context():
             output = pagure.utils.is_repo_committer(repo)
         self.assertFalse(output)
 
     def test_is_repo_committer_logged_in(self):
-        """ Test is_repo_committer in pagure with the appropriate user logged
-        in. """
+        """Test is_repo_committer in pagure with the appropriate user logged
+        in."""
         repo = pagure.lib.query._get_project(self.session, "test")
 
         g = munch.Munch()
@@ -78,8 +77,8 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
             self.assertTrue(output)
 
     def test_is_repo_committer_logged_in_in_group(self):
-        """ Test is_repo_committer in pagure with the appropriate user logged
-        in. """
+        """Test is_repo_committer in pagure with the appropriate user logged
+        in."""
         # Create group
         msg = pagure.lib.query.add_group(
             self.session,
@@ -127,8 +126,8 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
             self.assertTrue(output)
 
     def test_is_repo_committer_logged_in_in_ticket_group(self):
-        """ Test is_repo_committer in pagure with the appropriate user logged
-        in. """
+        """Test is_repo_committer in pagure with the appropriate user logged
+        in."""
         # Create group
         msg = pagure.lib.query.add_group(
             self.session,
@@ -180,8 +179,7 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
             self.assertFalse(output)
 
     def test_is_repo_committer_logged_in_wrong_user(self):
-        """ Test is_repo_committer in pagure with the wrong user logged in.
-        """
+        """Test is_repo_committer in pagure with the wrong user logged in."""
         repo = pagure.lib.query._get_project(self.session, "test")
 
         g = munch.Munch()
@@ -197,7 +195,7 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
 
     @mock.patch.dict("pagure.config.config", {"EXTERNAL_COMMITTER": config})
     def test_is_repo_committer_external_committer_generic_no_member(self):
-        """ Test is_repo_committer in pagure with EXTERNAL_COMMITTER
+        """Test is_repo_committer in pagure with EXTERNAL_COMMITTER
         configured to give access to all the provenpackager, but the user
         is not one.
         """
@@ -214,7 +212,7 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
 
     @mock.patch.dict("pagure.config.config", {"EXTERNAL_COMMITTER": config})
     def test_is_repo_committer_external_committer_generic_member(self):
-        """ Test is_repo_committer in pagure with EXTERNAL_COMMITTER
+        """Test is_repo_committer in pagure with EXTERNAL_COMMITTER
         configured to give access to all the provenpackager, and the user
         is one
         """
@@ -233,7 +231,7 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
 
     @mock.patch.dict("pagure.config.config", {"EXTERNAL_COMMITTER": config})
     def test_is_repo_committer_external_committer_excluding_one(self):
-        """ Test is_repo_committer in pagure with EXTERNAL_COMMITTER
+        """Test is_repo_committer in pagure with EXTERNAL_COMMITTER
         configured to give access to all the provenpackager but for this
         one repo
         """
@@ -250,7 +248,7 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
 
     @mock.patch.dict("pagure.config.config", {"EXTERNAL_COMMITTER": config})
     def test_is_repo_committer_owner_external_committer_excluding_one(self):
-        """ Test is_repo_committer in pagure with EXTERNAL_COMMITTER
+        """Test is_repo_committer in pagure with EXTERNAL_COMMITTER
         configured to give access to all the provenpackager but for this
         one repo, but the user is still a direct committer
         """
@@ -269,7 +267,7 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
 
     @mock.patch.dict("pagure.config.config", {"EXTERNAL_COMMITTER": config})
     def test_is_repo_committer_external_committer_restricted_not_member(self):
-        """ Test is_repo_committer in pagure with EXTERNAL_COMMITTER
+        """Test is_repo_committer in pagure with EXTERNAL_COMMITTER
         configured to give access the provenpackager just for one repo
         """
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -284,7 +282,7 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
 
     @mock.patch.dict("pagure.config.config", {"EXTERNAL_COMMITTER": config})
     def test_is_repo_committer_external_committer_restricting_to_one(self):
-        """ Test is_repo_committer in pagure with EXTERNAL_COMMITTER
+        """Test is_repo_committer in pagure with EXTERNAL_COMMITTER
         configured to give access the provenpackager just for one repo
         """
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -302,7 +300,7 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
     def test_is_repo_committer_external_committer_restricting_another_one(
         self,
     ):
-        """ Test is_repo_committer in pagure with EXTERNAL_COMMITTER
+        """Test is_repo_committer in pagure with EXTERNAL_COMMITTER
         configured to give access the provenpackager just for one repo not
         this one
         """
@@ -318,16 +316,15 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
             self.assertFalse(output)
 
     def test_is_repo_collaborator_logged_out(self):
-        """ Test is_repo_committer in pagure when there is no logged in user.
-        """
+        """Test is_repo_committer in pagure when there is no logged in user."""
         repo = pagure.lib.query._get_project(self.session, "test")
         with self.app.application.app_context():
             output = pagure.utils.is_repo_collaborator(repo, "master")
         self.assertFalse(output)
 
     def test_is_repo_collaborator_logged_in(self):
-        """ Test is_repo_collaborator in pagure with the appropriate user logged
-        in. """
+        """Test is_repo_collaborator in pagure with the appropriate user logged
+        in."""
         repo = pagure.lib.query._get_project(self.session, "test")
 
         g = munch.Munch()
@@ -341,8 +338,8 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
             self.assertTrue(output)
 
     def test_is_repo_collaborator_invalid_username(self):
-        """ Test is_repo_collaborator in pagure with the appropriate user logged
-        in. """
+        """Test is_repo_collaborator in pagure with the appropriate user logged
+        in."""
         repo = pagure.lib.query._get_project(self.session, "test")
 
         g = munch.Munch()
@@ -357,8 +354,8 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
 
     @mock.patch.dict("pagure.config.config", {"PAGURE_ADMIN_USERS": ["foo"]})
     def test_is_repo_collaborator_admin_user(self):
-        """ Test is_repo_collaborator in pagure with the appropriate user logged
-        in. """
+        """Test is_repo_collaborator in pagure with the appropriate user logged
+        in."""
         repo = pagure.lib.query._get_project(self.session, "test")
 
         g = munch.Munch()
@@ -372,8 +369,8 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
             self.assertTrue(output)
 
     def test_is_repo_collaborator_not_in_project(self):
-        """ Test is_repo_collaborator in pagure with the appropriate user logged
-        in. """
+        """Test is_repo_collaborator in pagure with the appropriate user logged
+        in."""
         repo = pagure.lib.query._get_project(self.session, "test")
 
         g = munch.Munch()
@@ -387,8 +384,8 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
             self.assertFalse(output)
 
     def test_is_repo_collaborator_in_project(self):
-        """ Test is_repo_collaborator in pagure with the appropriate user logged
-        in. """
+        """Test is_repo_collaborator in pagure with the appropriate user logged
+        in."""
         repo = pagure.lib.query._get_project(self.session, "test")
 
         # Add user foo to project test
@@ -431,8 +428,8 @@ class PagureGetRemoteRepoPath(tests.SimplePagureTest):
             self.assertTrue(output)
 
     def test_is_repo_collaborator_logged_in_in_group(self):
-        """ Test is_repo_committer in pagure with the appropriate user logged
-        in. """
+        """Test is_repo_committer in pagure with the appropriate user logged
+        in."""
         # Create group
         msg = pagure.lib.query.add_group(
             self.session,
diff --git a/tests/test_pagure_flask_api.py b/tests/test_pagure_flask_api.py
index 0cb90c6..50f5223 100644
--- a/tests/test_pagure_flask_api.py
+++ b/tests/test_pagure_flask_api.py
@@ -29,12 +29,12 @@ import tests
 
 
 class PagureFlaskApitests(tests.SimplePagureTest):
-    """ Tests for flask API controller of pagure """
+    """Tests for flask API controller of pagure"""
 
     maxDiff = None
 
     def test_api_doc(self):
-        """ Test the API documentation page. """
+        """Test the API documentation page."""
         print(dir(self.app))
         output = self.app.get("/api/0/")
         output_text = output.get_data(as_text=True)
@@ -42,7 +42,7 @@ class PagureFlaskApitests(tests.SimplePagureTest):
         self.assertIn(">Pagure API Reference</h5>\n", output_text)
 
     def test_api_doc_authenticated(self):
-        """ Test the API documentation page. """
+        """Test the API documentation page."""
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
             output = self.app.get("/api/0/")
@@ -68,7 +68,7 @@ class PagureFlaskApitests(tests.SimplePagureTest):
             self.assertEqual(pagure.api.get_request_data()["foo"], "bar")
 
     def test_api_version_old_url(self):
-        """ Test the api_version function.  """
+        """Test the api_version function."""
         output = self.app.get("/api/0/version")
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
@@ -76,7 +76,7 @@ class PagureFlaskApitests(tests.SimplePagureTest):
         self.assertEqual(sorted(data.keys()), ["version"])
 
     def test_api_version_new_url(self):
-        """ Test the api_version function at its new url.  """
+        """Test the api_version function at its new url."""
         output = self.app.get("/api/0/-/version")
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
@@ -84,7 +84,7 @@ class PagureFlaskApitests(tests.SimplePagureTest):
         self.assertEqual(sorted(data.keys()), ["version"])
 
     def test_api_groups(self):
-        """ Test the api_groups function.  """
+        """Test the api_groups function."""
 
         # Add a couple of groups so that we can list them
         item = pagure.lib.model.PagureGroup(
@@ -123,7 +123,7 @@ class PagureFlaskApitests(tests.SimplePagureTest):
         self.assertEqual(data["total_groups"], 1)
 
     def test_api_whoami_unauth(self):
-        """ Test the api_whoami function. """
+        """Test the api_whoami function."""
 
         output = self.app.post("/api/0/-/whoami")
         self.assertEqual(output.status_code, 401)
@@ -139,7 +139,7 @@ class PagureFlaskApitests(tests.SimplePagureTest):
         )
 
     def test_api_whoami_invalid_auth(self):
-        """ Test the api_whoami function with an invalid token. """
+        """Test the api_whoami function with an invalid token."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
 
@@ -160,7 +160,7 @@ class PagureFlaskApitests(tests.SimplePagureTest):
         )
 
     def test_api_whoami_auth(self):
-        """ Test the api_whoami function with a valid token. """
+        """Test the api_whoami function with a valid token."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
 
@@ -172,7 +172,7 @@ class PagureFlaskApitests(tests.SimplePagureTest):
         self.assertEqual(data, {"username": "pingou"})
 
     def test_api_error_codes(self):
-        """ Test the api_error_codes endpoint. """
+        """Test the api_error_codes endpoint."""
         output = self.app.get("/api/0/-/error_codes")
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
diff --git a/tests/test_pagure_flask_api_auth.py b/tests/test_pagure_flask_api_auth.py
index d5c49b6..0af0946 100644
--- a/tests/test_pagure_flask_api_auth.py
+++ b/tests/test_pagure_flask_api_auth.py
@@ -28,11 +28,10 @@ import tests
 
 
 class PagureFlaskApiAuthtests(tests.SimplePagureTest):
-    """ Tests for the authentication in the flask API of pagure """
+    """Tests for the authentication in the flask API of pagure"""
 
     def test_auth_no_data(self):
-        """ Test the authentication when there is nothing in the database.
-        """
+        """Test the authentication when there is nothing in the database."""
 
         output = self.app.post("/api/0/foo/new_issue")
         self.assertEqual(output.status_code, 401)
@@ -53,8 +52,7 @@ class PagureFlaskApiAuthtests(tests.SimplePagureTest):
         self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])
 
     def test_auth_noacl(self):
-        """ Test the authentication when the token does not have any ACL.
-        """
+        """Test the authentication when the token does not have any ACL."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
 
@@ -77,8 +75,7 @@ class PagureFlaskApiAuthtests(tests.SimplePagureTest):
         self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])
 
     def test_auth_expired(self):
-        """ Test the authentication when the token has expired.
-        """
+        """Test the authentication when the token has expired."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
 
@@ -101,8 +98,7 @@ class PagureFlaskApiAuthtests(tests.SimplePagureTest):
         self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])
 
     def test_auth(self):
-        """ Test the token based authentication.
-        """
+        """Test the token based authentication."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
diff --git a/tests/test_pagure_flask_api_boards.py b/tests/test_pagure_flask_api_boards.py
index ef12587..5c38669 100644
--- a/tests/test_pagure_flask_api_boards.py
+++ b/tests/test_pagure_flask_api_boards.py
@@ -63,6 +63,7 @@ def set_up_board(self):
             "boards": [
                 {
                     "active": True,
+                    "full_url": "http://localhost.localdomain/test/boards/dev",
                     "name": "dev",
                     "status": [],
                     "tag": {
@@ -77,7 +78,7 @@ def set_up_board(self):
 
 
 class PagureFlaskApiBoardstests(tests.SimplePagureTest):
-    """ Tests for flask API Boards controller of pagure """
+    """Tests for flask API Boards controller of pagure"""
 
     maxDiff = None
 
@@ -263,6 +264,7 @@ class PagureFlaskApiBoardstests(tests.SimplePagureTest):
                 "boards": [
                     {
                         "active": True,
+                        "full_url": "http://localhost.localdomain/test/boards/dev",
                         "name": "dev",
                         "status": [],
                         "tag": {
@@ -299,6 +301,7 @@ class PagureFlaskApiBoardstests(tests.SimplePagureTest):
                 "boards": [
                     {
                         "active": True,
+                        "full_url": "http://localhost.localdomain/test/boards/dev",
                         "name": "dev",
                         "status": [],
                         "tag": {
@@ -309,6 +312,7 @@ class PagureFlaskApiBoardstests(tests.SimplePagureTest):
                     },
                     {
                         "active": True,
+                        "full_url": "http://localhost.localdomain/test/boards/infra",
                         "name": "infra",
                         "status": [],
                         "tag": {
@@ -322,7 +326,11 @@ class PagureFlaskApiBoardstests(tests.SimplePagureTest):
         )
 
         # Remove one of the 2 boards
-        data = json.dumps({"dev": {"active": True, "tag": "dev"},})
+        data = json.dumps(
+            {
+                "dev": {"active": True, "tag": "dev"},
+            }
+        )
         output = self.app.post(
             "/api/0/test/boards", headers=headers, data=data
         )
@@ -334,6 +342,7 @@ class PagureFlaskApiBoardstests(tests.SimplePagureTest):
                 "boards": [
                     {
                         "active": True,
+                        "full_url": "http://localhost.localdomain/test/boards/dev",
                         "name": "dev",
                         "status": [],
                         "tag": {
@@ -390,7 +399,7 @@ class PagureFlaskApiBoardstests(tests.SimplePagureTest):
 
 
 class PagureFlaskApiBoardsWithBoardtests(tests.SimplePagureTest):
-    """ Tests for flask API Boards controller of pagure for the tests
+    """Tests for flask API Boards controller of pagure for the tests
     requiring a pre-existing board.
     """
 
@@ -422,6 +431,7 @@ class PagureFlaskApiBoardsWithBoardtests(tests.SimplePagureTest):
                 "boards": [
                     {
                         "active": False,
+                        "full_url": "http://localhost.localdomain/test/boards/dev",
                         "name": "dev",
                         "status": [],
                         "tag": {
@@ -441,7 +451,11 @@ class PagureFlaskApiBoardsWithBoardtests(tests.SimplePagureTest):
         }
 
         # Associate the existing board with an invalid tag
-        data = json.dumps({"dev": {"active": True, "tag": "invalid"},})
+        data = json.dumps(
+            {
+                "dev": {"active": True, "tag": "invalid"},
+            }
+        )
         output = self.app.post(
             "/api/0/test/boards", headers=headers, data=data
         )
@@ -515,7 +529,8 @@ class PagureFlaskApiBoardsWithBoardtests(tests.SimplePagureTest):
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
         self.assertDictEqual(
-            data, {"boards": []},
+            data,
+            {"boards": []},
         )
 
     def test_api_board_delete_html_input(self):
@@ -531,7 +546,8 @@ class PagureFlaskApiBoardsWithBoardtests(tests.SimplePagureTest):
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
         self.assertDictEqual(
-            data, {"boards": []},
+            data,
+            {"boards": []},
         )
 
     def test_api_board_api_board_status_no_data(self):
@@ -768,6 +784,7 @@ class PagureFlaskApiBoardsWithBoardtests(tests.SimplePagureTest):
             {
                 "board": {
                     "active": True,
+                    "full_url": "http://localhost.localdomain/test/boards/dev",
                     "name": "dev",
                     "status": [
                         {
@@ -801,6 +818,88 @@ class PagureFlaskApiBoardsWithBoardtests(tests.SimplePagureTest):
             },
         )
 
+    def test_api_board_api_board_status_no_close_status(self):
+        headers = {
+            "Authorization": "token aaabbbcccddd",
+            "Content-Type": "application/json",
+        }
+
+        data = json.dumps(
+            {
+                "Backlog": {
+                    "close": False,
+                    "close_status": None,
+                    "bg_color": "#FFB300",
+                    "default": True,
+                    "rank": 1,
+                },
+                "Triaged": {
+                    "close": False,
+                    "close_status": None,
+                    "bg_color": "#ca0dcd",
+                    "default": False,
+                    "rank": 2,
+                },
+                "Done": {
+                    "close": True,
+                    "close_status": None,
+                    "bg_color": "#34d240",
+                    "default": False,
+                    "rank": 4,
+                },
+                "  ": {
+                    "close": True,
+                    "close_status": None,
+                    "bg_color": "#34d240",
+                    "default": False,
+                    "rank": 5,
+                },
+            }
+        )
+        output = self.app.post(
+            "/api/0/test/boards/dev/status", headers=headers, data=data
+        )
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data,
+            {
+                "board": {
+                    "active": True,
+                    "full_url": "http://localhost.localdomain/test/boards/dev",
+                    "name": "dev",
+                    "status": [
+                        {
+                            "bg_color": "#FFB300",
+                            "close": False,
+                            "close_status": None,
+                            "default": True,
+                            "name": "Backlog",
+                        },
+                        {
+                            "bg_color": "#ca0dcd",
+                            "close": False,
+                            "close_status": None,
+                            "default": False,
+                            "name": "Triaged",
+                        },
+                        {
+                            "name": "Done",
+                            "close": True,
+                            "close_status": None,
+                            "default": False,
+                            "bg_color": "#34d240",
+                        },
+                    ],
+                    "tag": {
+                        "tag": "dev",
+                        "tag_color": "DeepBlueSky",
+                        "tag_description": "",
+                    },
+                }
+            },
+        )
+
     def test_api_board_api_board_status_adding_removing(self):
         headers = {
             "Authorization": "token aaabbbcccddd",
@@ -849,6 +948,7 @@ class PagureFlaskApiBoardsWithBoardtests(tests.SimplePagureTest):
             {
                 "board": {
                     "active": True,
+                    "full_url": "http://localhost.localdomain/test/boards/dev",
                     "name": "dev",
                     "status": [
                         {
@@ -917,6 +1017,7 @@ class PagureFlaskApiBoardsWithBoardtests(tests.SimplePagureTest):
             {
                 "board": {
                     "active": True,
+                    "full_url": "http://localhost.localdomain/test/boards/dev",
                     "name": "dev",
                     "status": [
                         {
@@ -952,7 +1053,7 @@ class PagureFlaskApiBoardsWithBoardtests(tests.SimplePagureTest):
 
 
 class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
-    """ Tests for flask API Boards controller of pagure for the tests
+    """Tests for flask API Boards controller of pagure for the tests
     requiring a pre-existing board and issues.
     """
 
@@ -1039,7 +1140,14 @@ class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
             "Content-Type": "application/json",
         }
 
-        data = json.dumps({"12": {"status": "In Progress", "rank": 2,}})
+        data = json.dumps(
+            {
+                "12": {
+                    "status": "In Progress",
+                    "rank": 2,
+                }
+            }
+        )
         output = self.app.post(
             "/api/0/test/boards/invalid/add_issue", headers=headers, data=data
         )
@@ -1102,7 +1210,13 @@ class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
             "Content-Type": "application/json",
         }
 
-        data = json.dumps({"12": {"rank": 2,}})
+        data = json.dumps(
+            {
+                "12": {
+                    "rank": 2,
+                }
+            }
+        )
         output = self.app.post(
             "/api/0/test/boards/dev/add_issue", headers=headers, data=data
         )
@@ -1159,6 +1273,7 @@ class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
             {
                 "board": {
                     "active": True,
+                    "full_url": "http://localhost.localdomain/test/boards/dev",
                     "name": "dev",
                     "status": [
                         {
@@ -1198,7 +1313,14 @@ class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
             "Content-Type": "application/json",
         }
 
-        data = json.dumps({"12": {"status": "In Progress", "rank": 2,}})
+        data = json.dumps(
+            {
+                "12": {
+                    "status": "In Progress",
+                    "rank": 2,
+                }
+            }
+        )
         output = self.app.post(
             "/api/0/test/boards/invalid/update_issue",
             headers=headers,
@@ -1263,7 +1385,13 @@ class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
             "Content-Type": "application/json",
         }
 
-        data = json.dumps({"12": {"rank": 2,}})
+        data = json.dumps(
+            {
+                "12": {
+                    "rank": 2,
+                }
+            }
+        )
         output = self.app.post(
             "/api/0/test/boards/dev/add_issue", headers=headers, data=data
         )
@@ -1320,6 +1448,7 @@ class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
             {
                 "board": {
                     "active": True,
+                    "full_url": "http://localhost.localdomain/test/boards/dev",
                     "name": "dev",
                     "status": [
                         {
@@ -1360,7 +1489,9 @@ class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
         }
 
         data = json.dumps(
-            {self.tickets_uid[1]: {"status": "Done", "rank": 2},}
+            {
+                self.tickets_uid[1]: {"status": "Done", "rank": 2},
+            }
         )
         output = self.app.post(
             "/api/0/test/boards/dev/update_issue", headers=headers, data=data
@@ -1374,7 +1505,9 @@ class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
         self.assertEqual(repo.issues[1].close_status, "Fixed")
 
         data = json.dumps(
-            {self.tickets_uid[1]: {"status": "In Progress", "rank": 2},}
+            {
+                self.tickets_uid[1]: {"status": "In Progress", "rank": 2},
+            }
         )
         output = self.app.post(
             "/api/0/test/boards/dev/update_issue", headers=headers, data=data
@@ -1395,7 +1528,11 @@ class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
             "Content-Type": "application/json",
         }
 
-        data = json.dumps({"2": {"status": "In Progress", "rank": 2},})
+        data = json.dumps(
+            {
+                "2": {"status": "In Progress", "rank": 2},
+            }
+        )
         output = self.app.post(
             "/api/0/test/boards/dev/add_issue", headers=headers, data=data
         )
@@ -1419,6 +1556,7 @@ class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
                 {
                     "board": {
                         "active": True,
+                        "full_url": "http://localhost.localdomain/test/boards/dev",
                         "name": "dev",
                         "status": [
                             {
@@ -1475,6 +1613,7 @@ class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
                     "user": {
                         "default_email": "bar@pingou.com",
                         "emails": ["bar@pingou.com", "foo@pingou.com"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -1485,6 +1624,7 @@ class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
             "custom_fields": [],
             "date_created": "1594654596",
             "depends": [],
+            "full_url": "http://localhost.localdomain/test/issue/2",
             "id": 2,
             "last_updated": "1594654596",
             "milestone": None,
@@ -1497,6 +1637,7 @@ class PagureFlaskApiBoardsWithBoardAndIssuetests(tests.SimplePagureTest):
             "user": {
                 "default_email": "foo@bar.com",
                 "emails": ["foo@bar.com"],
+                "full_url": "http://localhost.localdomain/user/foo",
                 "fullname": "foo bar",
                 "name": "foo",
                 "url_path": "user/foo",
diff --git a/tests/test_pagure_flask_api_fork.py b/tests/test_pagure_flask_api_fork.py
index 7b5ebb9..456942d 100644
--- a/tests/test_pagure_flask_api_fork.py
+++ b/tests/test_pagure_flask_api_fork.py
@@ -29,20 +29,20 @@ import tests
 
 
 class PagureFlaskApiForktests(tests.Modeltests):
-    """ Tests for the flask API of pagure for issue """
+    """Tests for the flask API of pagure for issue"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiForktests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_views_pr_disabled(self):
-        """ Test the api_pull_request_views method of the flask api when PR
-        are disabled. """
+        """Test the api_pull_request_views method of the flask api when PR
+        are disabled."""
 
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
@@ -86,8 +86,8 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_views_pr_closed(self):
-        """ Test the api_pull_request_views method of the flask api to list
-        the closed PRs. """
+        """Test the api_pull_request_views method of the flask api to list
+        the closed PRs."""
 
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
@@ -266,8 +266,8 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_views_all_pr(self):
-        """ Test the api_pull_request_views method of the flask api to list
-        all PRs. """
+        """Test the api_pull_request_views method of the flask api to list
+        all PRs."""
 
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
@@ -338,7 +338,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_views(self, send_email):
-        """ Test the api_pull_request_views method of the flask api. """
+        """Test the api_pull_request_views method of the flask api."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -416,6 +416,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                     "commit_start": None,
                     "commit_stop": None,
                     "date_created": "1431414800",
+                    "full_url": "http://localhost.localdomain/test/pull-request/1",
                     "id": 1,
                     "initial_comment": None,
                     "last_updated": "1431414800",
@@ -443,6 +444,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                         "date_created": "1431414800",
                         "date_modified": "1431414800",
                         "description": "test project #1",
+                        "full_url": "http://localhost.localdomain/test",
                         "fullname": "test",
                         "url_path": "test",
                         "id": 1,
@@ -453,6 +455,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                         "priorities": {},
                         "tags": [],
                         "user": {
+                            "full_url": "http://localhost.localdomain/user/pingou",
                             "fullname": "PY C",
                             "name": "pingou",
                             "url_path": "user/pingou",
@@ -483,6 +486,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                         "date_created": "1431414800",
                         "date_modified": "1431414800",
                         "description": "test project #1",
+                        "full_url": "http://localhost.localdomain/test",
                         "fullname": "test",
                         "url_path": "test",
                         "id": 1,
@@ -493,6 +497,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                         "priorities": {},
                         "tags": [],
                         "user": {
+                            "full_url": "http://localhost.localdomain/user/pingou",
                             "fullname": "PY C",
                             "name": "pingou",
                             "url_path": "user/pingou",
@@ -505,6 +510,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                     "uid": "1431414800",
                     "updated_on": "1431414800",
                     "user": {
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -536,8 +542,8 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_view_tag_filtered(self, send_email):
-        """ Test the api_pull_request_view method of the flask api to list
-            tag filtered open PRs. """
+        """Test the api_pull_request_view method of the flask api to list
+        tag filtered open PRs."""
         send_email.return_value = True
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
@@ -656,7 +662,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_view_pr_disabled(self, send_email):
-        """ Test the api_pull_request_view method of the flask api. """
+        """Test the api_pull_request_view method of the flask api."""
         send_email.return_value = True
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
@@ -700,7 +706,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_view(self, send_email):
-        """ Test the api_pull_request_view method of the flask api. """
+        """Test the api_pull_request_view method of the flask api."""
         send_email.return_value = True
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
@@ -763,6 +769,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
             "commit_start": None,
             "commit_stop": None,
             "date_created": "1431414800",
+            "full_url": "http://localhost.localdomain/test/pull-request/1",
             "id": 1,
             "initial_comment": None,
             "last_updated": "1431414800",
@@ -790,6 +797,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                 "date_created": "1431414800",
                 "date_modified": "1431414800",
                 "description": "test project #1",
+                "full_url": "http://localhost.localdomain/test",
                 "fullname": "test",
                 "url_path": "test",
                 "id": 1,
@@ -800,6 +808,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                 "priorities": {},
                 "tags": [],
                 "user": {
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "fullname": "PY C",
                     "name": "pingou",
                     "url_path": "user/pingou",
@@ -830,6 +839,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                 "date_created": "1431414800",
                 "date_modified": "1431414800",
                 "description": "test project #1",
+                "full_url": "http://localhost.localdomain/test",
                 "fullname": "test",
                 "url_path": "test",
                 "id": 1,
@@ -840,6 +850,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                 "priorities": {},
                 "tags": [],
                 "user": {
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "fullname": "PY C",
                     "name": "pingou",
                     "url_path": "user/pingou",
@@ -852,6 +863,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
             "uid": "1431414800",
             "updated_on": "1431414800",
             "user": {
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "fullname": "PY C",
                 "name": "pingou",
                 "url_path": "user/pingou",
@@ -878,7 +890,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_by_uid_view(self, send_email):
-        """ Test the api_pull_request_by_uid_view method of the flask api. """
+        """Test the api_pull_request_by_uid_view method of the flask api."""
         send_email.return_value = True
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
@@ -933,6 +945,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
             "commit_start": None,
             "commit_stop": None,
             "date_created": "1431414800",
+            "full_url": "http://localhost.localdomain/test/pull-request/1",
             "id": 1,
             "initial_comment": None,
             "last_updated": "1431414800",
@@ -960,6 +973,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                 "date_created": "1431414800",
                 "date_modified": "1431414800",
                 "description": "test project #1",
+                "full_url": "http://localhost.localdomain/test",
                 "fullname": "test",
                 "url_path": "test",
                 "id": 1,
@@ -970,6 +984,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                 "priorities": {},
                 "tags": [],
                 "user": {
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "fullname": "PY C",
                     "name": "pingou",
                     "url_path": "user/pingou",
@@ -1000,6 +1015,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                 "date_created": "1431414800",
                 "date_modified": "1431414800",
                 "description": "test project #1",
+                "full_url": "http://localhost.localdomain/test",
                 "fullname": "test",
                 "url_path": "test",
                 "id": 1,
@@ -1010,6 +1026,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                 "priorities": {},
                 "tags": [],
                 "user": {
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "fullname": "PY C",
                     "name": "pingou",
                     "url_path": "user/pingou",
@@ -1022,6 +1039,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
             "uid": uid,
             "updated_on": "1431414800",
             "user": {
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "fullname": "PY C",
                 "name": "pingou",
                 "url_path": "user/pingou",
@@ -1049,7 +1067,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_close_pr_disabled(self, send_email):
-        """ Test the api_pull_request_close method of the flask api. """
+        """Test the api_pull_request_close method of the flask api."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1096,9 +1114,69 @@ class PagureFlaskApiForktests(tests.Modeltests):
             },
         )
 
+    @patch("pagure.lib.notify.send_email")
+    def test_api_pull_request_close_cross_project_token(self, send_email):
+        """Test the api_pull_request_close method of the flask api for cross-project API token."""
+        send_email.return_value = True
+
+        tests.create_projects(self.session)
+        tests.create_tokens(self.session)
+        tests.create_tokens_acl(self.session)
+
+        # Create the pull-request to close
+        repo = pagure.lib.query.get_authorized_project(self.session, "test")
+        forked_repo = pagure.lib.query.get_authorized_project(
+            self.session, "test"
+        )
+        req = pagure.lib.query.new_pull_request(
+            session=self.session,
+            repo_from=forked_repo,
+            branch_from="master",
+            repo_to=repo,
+            branch_to="master",
+            title="test pull-request",
+            user="foo",
+        )
+        self.session.commit()
+        self.assertEqual(req.id, 1)
+        self.assertEqual(req.title, "test pull-request")
+        self.assertEqual(req.user.id, 2)
+
+        # Create a token for foo
+        item = pagure.lib.model.Token(
+            id="foobar_token",
+            user_id=2,
+            project_id=None,
+            expiration=datetime.datetime.utcnow()
+            + datetime.timedelta(days=30),
+        )
+        self.session.add(item)
+        self.session.commit()
+
+        # Allow the token to close PR
+        acls = pagure.lib.query.get_acls(self.session)
+        for acl in acls:
+            if acl.name == "pull_request_close":
+                break
+        item = pagure.lib.model.TokenAcl(
+            token_id="foobar_token", acl_id=acl.id
+        )
+        self.session.add(item)
+        self.session.commit()
+
+        headers = {"Authorization": "token foobar_token"}
+
+        # User is the same that created this PR
+        output = self.app.post(
+            "/api/0/test/pull-request/1/close", headers=headers
+        )
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, {"message": "Pull-request closed!"})
+
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_close(self, send_email):
-        """ Test the api_pull_request_close method of the flask api. """
+        """Test the api_pull_request_close method of the flask api."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1205,10 +1283,127 @@ class PagureFlaskApiForktests(tests.Modeltests):
         data = json.loads(output.get_data(as_text=True))
         self.assertDictEqual(data, {"message": "Pull-request closed!"})
 
+    @patch("pagure.lib.notify.send_email")
+    def test_api_pull_request_reopen(self, send_email):
+        """Test the api_pull_request_reopen method of the flask api."""
+        send_email.return_value = True
+
+        tests.create_projects(self.session)
+        tests.create_tokens(self.session)
+        tests.create_tokens_acl(self.session)
+
+        # Create the pull-request to close and reopen
+        repo = pagure.lib.query.get_authorized_project(self.session, "test")
+        forked_repo = pagure.lib.query.get_authorized_project(
+            self.session, "test"
+        )
+        req = pagure.lib.query.new_pull_request(
+            session=self.session,
+            repo_from=forked_repo,
+            branch_from="master",
+            repo_to=repo,
+            branch_to="master",
+            title="test pull-request",
+            user="pingou",
+        )
+        self.session.commit()
+        self.assertEqual(req.id, 1)
+        self.assertEqual(req.title, "test pull-request")
+
+        headers = {"Authorization": "token aaabbbcccddd"}
+
+        # Invalid project
+        output = self.app.post(
+            "/api/0/foo/pull-request/1/close", headers=headers
+        )
+        self.assertEqual(output.status_code, 404)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data, {"error": "Project not found", "error_code": "ENOPROJECT"}
+        )
+
+        # Valid token, wrong project
+        output = self.app.post(
+            "/api/0/test2/pull-request/1/close", headers=headers
+        )
+        self.assertEqual(output.status_code, 401)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertEqual(
+            pagure.api.APIERROR.EINVALIDTOK.name, data["error_code"]
+        )
+        self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])
+
+        # Invalid PR
+        output = self.app.post(
+            "/api/0/test/pull-request/2/close", headers=headers
+        )
+        self.assertEqual(output.status_code, 404)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data, {"error": "Pull-Request not found", "error_code": "ENOREQ"}
+        )
+
+        # Create a token for foo for this project
+        item = pagure.lib.model.Token(
+            id="foobar_token",
+            user_id=2,
+            project_id=1,
+            expiration=datetime.datetime.utcnow()
+            + datetime.timedelta(days=30),
+        )
+        self.session.add(item)
+        self.session.commit()
+
+        # Allow the token to close and reopen PR
+        acls = pagure.lib.query.get_acls(self.session)
+        for acl in acls:
+            if acl.name == "pull_request_close":
+                break
+        item = pagure.lib.model.TokenAcl(
+            token_id="foobar_token", acl_id=acl.id
+        )
+        self.session.add(item)
+        self.session.commit()
+
+        headers = {"Authorization": "token foobar_token"}
+
+        # User not admin
+        output = self.app.post(
+            "/api/0/test/pull-request/1/close", headers=headers
+        )
+        self.assertEqual(output.status_code, 403)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data,
+            {
+                "error": "You are not allowed to merge/close pull-request "
+                "for this project",
+                "error_code": "ENOPRCLOSE",
+            },
+        )
+
+        headers = {"Authorization": "token aaabbbcccddd"}
+
+        # Close PR
+        output = self.app.post(
+            "/api/0/test/pull-request/1/close", headers=headers
+        )
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, {"message": "Pull-request closed!"})
+
+        # Reopen PR
+        output = self.app.post(
+            "/api/0/test/pull-request/1/reopen", headers=headers
+        )
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, {"message": "Pull-request reopened!"})
+
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_merge_pr_disabled(self, send_email):
-        """ Test the api_pull_request_merge method of the flask api when PR
-        are disabled. """
+        """Test the api_pull_request_merge method of the flask api when PR
+        are disabled."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1265,8 +1460,8 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_merge_only_assigned(self, send_email):
-        """ Test the api_pull_request_merge method of the flask api when
-        only assignee can merge the PR and the PR isn't assigned. """
+        """Test the api_pull_request_merge method of the flask api when
+        only assignee can merge the PR and the PR isn't assigned."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1325,9 +1520,9 @@ class PagureFlaskApiForktests(tests.Modeltests):
     def test_api_pull_request_merge_only_assigned_not_assignee(
         self, send_email
     ):
-        """ Test the api_pull_request_merge method of the flask api when
+        """Test the api_pull_request_merge method of the flask api when
         only assignee can merge the PR and the PR isn't assigned to the
-        user asking to merge. """
+        user asking to merge."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1387,8 +1582,8 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_merge_minimal_score(self, send_email):
-        """ Test the api_pull_request_merge method of the flask api when
-        a PR requires a certain minimal score to be merged. """
+        """Test the api_pull_request_merge method of the flask api when
+        a PR requires a certain minimal score to be merged."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1446,7 +1641,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_merge(self, send_email):
-        """ Test the api_pull_request_merge method of the flask api. """
+        """Test the api_pull_request_merge method of the flask api."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1563,7 +1758,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_merge_conflicting(self, send_email):
-        """ Test the api_pull_request_merge method of the flask api. """
+        """Test the api_pull_request_merge method of the flask api."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1641,7 +1836,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_merge_user_token(self, send_email):
-        """ Test the api_pull_request_merge method of the flask api. """
+        """Test the api_pull_request_merge method of the flask api."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1758,7 +1953,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_add_comment(self, mockemail):
-        """ Test the api_pull_request_add_comment method of the flask api. """
+        """Test the api_pull_request_add_comment method of the flask api."""
         mockemail.return_value = True
 
         tests.create_projects(self.session)
@@ -1866,8 +2061,8 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_add_comment_wrong_user(self):
-        """ Test the api_pull_request_add_comment method of the flask api
-        when the user is not found in the DB. """
+        """Test the api_pull_request_add_comment method of the flask api
+        when the user is not found in the DB."""
 
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
@@ -1913,8 +2108,8 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_add_comment_pr_disabled(self):
-        """ Test the api_pull_request_add_comment method of the flask api
-        when PRs are disabled. """
+        """Test the api_pull_request_add_comment method of the flask api
+        when PRs are disabled."""
 
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
@@ -1972,7 +2167,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pull_request_add_comment_user_token(self, mockemail):
-        """ Test the api_pull_request_add_comment method of the flask api. """
+        """Test the api_pull_request_add_comment method of the flask api."""
         mockemail.return_value = True
 
         tests.create_projects(self.session)
@@ -2079,7 +2274,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_subscribe_pull_request_pr_disabled(self, p_send_email):
-        """ Test the api_subscribe_pull_request method of the flask api. """
+        """Test the api_subscribe_pull_request method of the flask api."""
         p_send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -2114,7 +2309,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
     def test_api_subscribe_pull_request_invalid_token(
         self, p_send_email, p_ugt
     ):
-        """ Test the api_subscribe_pull_request method of the flask api. """
+        """Test the api_subscribe_pull_request method of the flask api."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -2180,7 +2375,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_api_subscribe_pull_request(self, p_send_email, p_ugt):
-        """ Test the api_subscribe_pull_request method of the flask api. """
+        """Test the api_subscribe_pull_request method of the flask api."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -2373,8 +2568,8 @@ class PagureFlaskApiForktests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_api_subscribe_pull_request_logged_in(self, p_send_email, p_ugt):
-        """ Test the api_subscribe_pull_request method of the flask api
-        when the user is logged in via the UI. """
+        """Test the api_subscribe_pull_request method of the flask api
+        when the user is logged in via the UI."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -2450,7 +2645,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_open_invalid_project(self):
-        """ Test the api_pull_request_create method of the flask api when
+        """Test the api_pull_request_create method of the flask api when
         not the project doesn't exist.
         """
 
@@ -2484,7 +2679,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_open_missing_title(self):
-        """ Test the api_pull_request_create method of the flask api when
+        """Test the api_pull_request_create method of the flask api when
         not title is submitted.
         """
 
@@ -2523,7 +2718,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_open_missing_branch_to(self):
-        """ Test the api_pull_request_create method of the flask api when
+        """Test the api_pull_request_create method of the flask api when
         not branch to is submitted.
         """
 
@@ -2562,7 +2757,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_open_missing_branch_from(self):
-        """ Test the api_pull_request_create method of the flask api when
+        """Test the api_pull_request_create method of the flask api when
         not branch from is submitted.
         """
 
@@ -2601,7 +2796,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_open_pr_disabled(self):
-        """ Test the api_pull_request_create method of the flask api when
+        """Test the api_pull_request_create method of the flask api when
         the parent repo disabled pull-requests.
         """
 
@@ -2648,7 +2843,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_open_signed_pr(self):
-        """ Test the api_pull_request_create method of the flask api when
+        """Test the api_pull_request_create method of the flask api when
         the parent repo enforces signed-off pull-requests.
         """
 
@@ -2696,7 +2891,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_open_invalid_branch_from(self):
-        """ Test the api_pull_request_create method of the flask api when
+        """Test the api_pull_request_create method of the flask api when
         the branch from does not exist.
         """
 
@@ -2744,7 +2939,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_open_invalid_token(self):
-        """ Test the api_pull_request_create method of the flask api when
+        """Test the api_pull_request_create method of the flask api when
         queried with an invalid token.
         """
 
@@ -2785,7 +2980,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_open_invalid_access(self):
-        """ Test the api_pull_request_create method of the flask api when
+        """Test the api_pull_request_create method of the flask api when
         the user opening the PR doesn't have commit access.
         """
 
@@ -2825,7 +3020,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_open_invalid_branch_to(self):
-        """ Test the api_pull_request_create method of the flask api when
+        """Test the api_pull_request_create method of the flask api when
         the branch to does not exist.
         """
 
@@ -2941,7 +3136,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_open_from_branch_to_origin(self):
         """Test the api_pull_request_create method from a fork to a master,
-       with project token of a origin with all the acls"""
+        with project token of a origin with all the acls"""
 
         tests.create_projects(self.session)
         tests.create_projects(
@@ -3010,7 +3205,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_open(self):
-        """ Test the api_pull_request_create method of the flask api. """
+        """Test the api_pull_request_create method of the flask api."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -3060,6 +3255,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                 "commit_start": "114f1b468a5f05e635fcb6394273f3f907386eab",
                 "commit_stop": "114f1b468a5f05e635fcb6394273f3f907386eab",
                 "date_created": "1516348115",
+                "full_url": "http://localhost.localdomain/test/pull-request/1",
                 "id": 1,
                 "initial_comment": "Nothing much, the changes speak for themselves",
                 "last_updated": "1516348115",
@@ -3087,6 +3283,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                     "date_created": "1516348115",
                     "date_modified": "1516348115",
                     "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
                     "fullname": "test",
                     "id": 1,
                     "milestones": {},
@@ -3097,6 +3294,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                     "tags": [],
                     "url_path": "test",
                     "user": {
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -3127,6 +3325,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                     "date_created": "1516348115",
                     "date_modified": "1516348115",
                     "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
                     "fullname": "test",
                     "id": 1,
                     "milestones": {},
@@ -3137,6 +3336,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                     "tags": [],
                     "url_path": "test",
                     "user": {
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -3149,6 +3349,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                 "uid": "e8b68df8711648deac67c3afed15a798",
                 "updated_on": "1516348115",
                 "user": {
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "fullname": "PY C",
                     "name": "pingou",
                     "url_path": "user/pingou",
@@ -3158,7 +3359,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_open_missing_initial_comment(self):
-        """ Test the api_pull_request_create method of the flask api when
+        """Test the api_pull_request_create method of the flask api when
         not initial comment is submitted.
         """
 
@@ -3209,6 +3410,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                 "commit_start": "114f1b468a5f05e635fcb6394273f3f907386eab",
                 "commit_stop": "114f1b468a5f05e635fcb6394273f3f907386eab",
                 "date_created": "1516348115",
+                "full_url": "http://localhost.localdomain/test/pull-request/1",
                 "id": 1,
                 "initial_comment": None,
                 "last_updated": "1516348115",
@@ -3236,6 +3438,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                     "date_created": "1516348115",
                     "date_modified": "1516348115",
                     "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
                     "fullname": "test",
                     "id": 1,
                     "milestones": {},
@@ -3246,6 +3449,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                     "tags": [],
                     "url_path": "test",
                     "user": {
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -3276,6 +3480,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                     "date_created": "1516348115",
                     "date_modified": "1516348115",
                     "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
                     "fullname": "test",
                     "id": 1,
                     "milestones": {},
@@ -3286,6 +3491,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                     "tags": [],
                     "url_path": "test",
                     "user": {
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -3298,6 +3504,7 @@ class PagureFlaskApiForktests(tests.Modeltests):
                 "uid": "e8b68df8711648deac67c3afed15a798",
                 "updated_on": "1516348115",
                 "user": {
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "fullname": "PY C",
                     "name": "pingou",
                     "url_path": "user/pingou",
@@ -3307,13 +3514,12 @@ class PagureFlaskApiForktests(tests.Modeltests):
 
 
 class PagureFlaskApiForkPRDiffStatstests(tests.Modeltests):
-    """ Tests for the flask API of pagure for the diff stats endpoint of PRs
-    """
+    """Tests for the flask API of pagure for the diff stats endpoint of PRs"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiForkPRDiffStatstests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -3349,7 +3555,7 @@ class PagureFlaskApiForkPRDiffStatstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_diffstats_no_repo(self):
-        """ Test the api_pull_request_merge method of the flask api. """
+        """Test the api_pull_request_merge method of the flask api."""
         output = self.app.get("/api/0/invalid/pull-request/404/diffstats")
         self.assertEqual(output.status_code, 404)
         data = json.loads(output.get_data(as_text=True))
@@ -3360,7 +3566,7 @@ class PagureFlaskApiForkPRDiffStatstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_diffstats_no_pr(self):
-        """ Test the api_pull_request_merge method of the flask api. """
+        """Test the api_pull_request_merge method of the flask api."""
         output = self.app.get("/api/0/test/pull-request/404/diffstats")
         self.assertEqual(output.status_code, 404)
         data = json.loads(output.get_data(as_text=True))
@@ -3371,7 +3577,7 @@ class PagureFlaskApiForkPRDiffStatstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_diffstats_file_modified(self):
-        """ Test the api_pull_request_merge method of the flask api. """
+        """Test the api_pull_request_merge method of the flask api."""
         output = self.app.get("/api/0/test/pull-request/1/diffstats")
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
@@ -3392,7 +3598,7 @@ class PagureFlaskApiForkPRDiffStatstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_diffstats_file_added_mofidied(self):
-        """ Test the api_pull_request_merge method of the flask api. """
+        """Test the api_pull_request_merge method of the flask api."""
         tests.add_commit_git_repo(
             os.path.join(self.path, "repos", "test.git"), ncommits=5
         )
@@ -3453,7 +3659,7 @@ class PagureFlaskApiForkPRDiffStatstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_diffstats_file_modified_deleted(self):
-        """ Test the api_pull_request_merge method of the flask api. """
+        """Test the api_pull_request_merge method of the flask api."""
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         self.assertEqual(len(repo.requests), 1)
         pagure.lib.tasks.update_pull_request(repo.requests[0].uid)
@@ -3500,8 +3706,7 @@ class PagureFlaskApiForkPRDiffStatstests(tests.Modeltests):
 
 
 class PagureApiThresholdReachedTests(tests.Modeltests):
-    """ Test the behavior of the threshold_reached value returned by the API.
-    """
+    """Test the behavior of the threshold_reached value returned by the API."""
 
     maxDiff = None
 
@@ -3521,7 +3726,7 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environment for the tests. """
+        """Set up the environment for the tests."""
         super(PagureApiThresholdReachedTests, self).setUp()
 
         tests.create_projects(self.session)
@@ -3580,6 +3785,7 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
             "commit_start": "114f1b468a5f05e635fcb6394273f3f907386eab",
             "commit_stop": "114f1b468a5f05e635fcb6394273f3f907386eab",
             "date_created": "1516348115",
+            "full_url": "http://localhost.localdomain/test/pull-request/1",
             "id": 1,
             "initial_comment": "Nothing much, the changes speak for themselves",
             "last_updated": "1516348115",
@@ -3607,6 +3813,7 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
                 "date_created": "1516348115",
                 "date_modified": "1516348115",
                 "description": "test project #1",
+                "full_url": "http://localhost.localdomain/test",
                 "fullname": "test",
                 "id": 1,
                 "milestones": {},
@@ -3617,6 +3824,7 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
                 "tags": [],
                 "url_path": "test",
                 "user": {
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "fullname": "PY C",
                     "name": "pingou",
                     "url_path": "user/pingou",
@@ -3647,6 +3855,7 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
                 "date_created": "1516348115",
                 "date_modified": "1516348115",
                 "description": "test project #1",
+                "full_url": "http://localhost.localdomain/test",
                 "fullname": "test",
                 "id": 1,
                 "milestones": {},
@@ -3657,6 +3866,7 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
                 "tags": [],
                 "url_path": "test",
                 "user": {
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "fullname": "PY C",
                     "name": "pingou",
                     "url_path": "user/pingou",
@@ -3669,6 +3879,7 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
             "uid": "e8b68df8711648deac67c3afed15a798",
             "updated_on": "1516348115",
             "user": {
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "fullname": "PY C",
                 "name": "pingou",
                 "url_path": "user/pingou",
@@ -3676,8 +3887,7 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
         }
 
     def test_api_pull_request_no_comments(self):
-        """ Check the value of threshold_reach when the PR has no comments.
-        """
+        """Check the value of threshold_reach when the PR has no comments."""
 
         # Check the PR with 0 comment:
         output = self.app.get("/api/0/test/pull-request/1")
@@ -3688,8 +3898,7 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
         self.assertDictEqual(data, self.expected_data)
 
     def test_api_pull_request_one_comments(self):
-        """ Check the value of threshold_reach when the PR has one comment.
-        """
+        """Check the value of threshold_reach when the PR has one comment."""
         # Check the PR with 1 comment:
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {"comment": "This is a very interesting solution :thumbsup:"}
@@ -3708,7 +3917,7 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
         self.assertDictEqual(data, self.expected_data)
 
     def test_api_pull_request_two_comments_one_person(self):
-        """ Check the value of threshold_reach when the PR has two comments
+        """Check the value of threshold_reach when the PR has two comments
         but from the same person.
         """
         # Add two comments from the same user:
@@ -3738,7 +3947,7 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
         self.assertDictEqual(data, self.expected_data)
 
     def test_api_pull_request_two_comments_two_persons(self):
-        """ Check the value of threshold_reach when the PR has two comments
+        """Check the value of threshold_reach when the PR has two comments
         from two different persons.
         """
         # Add two comments from two users:
@@ -3769,7 +3978,7 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
         self.assertDictEqual(data, self.expected_data)
 
     def test_api_pull_request_three_comments_two_persons_changed_to_no(self):
-        """ Check the value of threshold_reach when the PR has three
+        """Check the value of threshold_reach when the PR has three
         comments from two person among which one changed their mind from
         +1 to -1.
         """
@@ -3811,7 +4020,7 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
         self.assertDictEqual(data, self.expected_data)
 
     def test_api_pull_request_three_comments_two_persons_changed_to_yes(self):
-        """ Check the value of threshold_reach when the PR has three
+        """Check the value of threshold_reach when the PR has three
         comments from two person among which one changed their mind from
         -1 to +1
         """
@@ -3852,13 +4061,12 @@ class PagureApiThresholdReachedTests(tests.Modeltests):
 
 
 class PagureFlaskApiForkGetCommenttests(tests.Modeltests):
-    """ Tests for the flask API of pagure for the comment endpoint of PRs
-    """
+    """Tests for the flask API of pagure for the comment endpoint of PRs"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environment, ran before every tests. """
+        """Set up the environment, ran before every tests."""
         super(PagureFlaskApiForkGetCommenttests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -3908,7 +4116,7 @@ class PagureFlaskApiForkGetCommenttests(tests.Modeltests):
         self.assertEqual(req.comments[0].id, 1)
 
     def test_api_pull_request_get_comment_not_found(self):
-        """ Test the api_pull_request_get_comment method of the flask api. """
+        """Test the api_pull_request_get_comment method of the flask api."""
         output = self.app.get("/api/0/test/pull-request/1/comment/2")
         self.assertEqual(output.status_code, 404)
         data = json.loads(output.get_data(as_text=True))
@@ -3917,7 +4125,7 @@ class PagureFlaskApiForkGetCommenttests(tests.Modeltests):
         )
 
     def test_api_pull_request_get_comment(self):
-        """ Test the api_pull_request_get_comment method of the flask api. """
+        """Test the api_pull_request_get_comment method of the flask api."""
         output = self.app.get("/api/0/test/pull-request/1/comment/1")
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
diff --git a/tests/test_pagure_flask_api_fork_assign.py b/tests/test_pagure_flask_api_fork_assign.py
index 014f239..01a069f 100644
--- a/tests/test_pagure_flask_api_fork_assign.py
+++ b/tests/test_pagure_flask_api_fork_assign.py
@@ -34,14 +34,14 @@ import tests
 
 
 class PagureFlaskApiForkAssigntests(tests.SimplePagureTest):
-    """ Tests for the flask API of pagure for assigning a PR """
+    """Tests for the flask API of pagure for assigning a PR"""
 
     maxDiff = None
 
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiForkAssigntests, self).setUp()
 
         tests.create_projects(self.session)
@@ -101,8 +101,7 @@ class PagureFlaskApiForkAssigntests(tests.SimplePagureTest):
         self.assertEqual(output.status_code, 200)
 
     def test_api_assign_pr_invalid_project_namespace(self):
-        """ Test api_pull_request_assign method when the project doesn't exist.
-        """
+        """Test api_pull_request_assign method when the project doesn't exist."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -123,8 +122,7 @@ class PagureFlaskApiForkAssigntests(tests.SimplePagureTest):
         )
 
     def test_api_assign_pr_invalid_project(self):
-        """ Test api_pull_request_assign method when the project doesn't exist.
-        """
+        """Test api_pull_request_assign method when the project doesn't exist."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -139,7 +137,7 @@ class PagureFlaskApiForkAssigntests(tests.SimplePagureTest):
         )
 
     def test_api_assign_pr_invalid_project_token(self):
-        """ Test api_pull_request_assign method when the token doesn't correspond
+        """Test api_pull_request_assign method when the token doesn't correspond
         to the project.
         """
 
@@ -158,8 +156,7 @@ class PagureFlaskApiForkAssigntests(tests.SimplePagureTest):
         )
 
     def test_api_assign_pr_invalid_pr(self):
-        """ Test api_pull_request_assign method when asking for an invalid PR
-        """
+        """Test api_pull_request_assign method when asking for an invalid PR"""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -174,8 +171,7 @@ class PagureFlaskApiForkAssigntests(tests.SimplePagureTest):
         )
 
     def test_api_assign_pr_no_input(self):
-        """ Test api_pull_request_assign method when no input is specified
-        """
+        """Test api_pull_request_assign method when no input is specified"""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -188,8 +184,7 @@ class PagureFlaskApiForkAssigntests(tests.SimplePagureTest):
         self.assertDictEqual(data, {"message": "Nothing to change"})
 
     def test_api_assign_pr_assigned(self):
-        """ Test api_pull_request_assign method when with valid input
-        """
+        """Test api_pull_request_assign method when with valid input"""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -204,8 +199,7 @@ class PagureFlaskApiForkAssigntests(tests.SimplePagureTest):
         self.assertDictEqual(data, {"message": "Request assigned"})
 
     def test_api_assign_pr_unassigned(self):
-        """ Test api_pull_request_assign method when unassigning
-        """
+        """Test api_pull_request_assign method when unassigning"""
         self.test_api_assign_pr_assigned()
 
         headers = {"Authorization": "token aaabbbcccddd"}
@@ -220,8 +214,7 @@ class PagureFlaskApiForkAssigntests(tests.SimplePagureTest):
         self.assertDictEqual(data, {"message": "Request assignee reset"})
 
     def test_api_assign_pr_unassigned_twice(self):
-        """ Test api_pull_request_assign method when unassigning
-        """
+        """Test api_pull_request_assign method when unassigning"""
         self.test_api_assign_pr_unassigned()
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {"assignee": None}
@@ -235,7 +228,7 @@ class PagureFlaskApiForkAssigntests(tests.SimplePagureTest):
         self.assertDictEqual(data, {"message": "Nothing to change"})
 
     def test_api_assign_pr_unassigned_empty_string(self):
-        """ Test api_pull_request_assign method when unassigning with an
+        """Test api_pull_request_assign method when unassigning with an
         empty string
         """
         self.test_api_assign_pr_assigned()
diff --git a/tests/test_pagure_flask_api_fork_update.py b/tests/test_pagure_flask_api_fork_update.py
index d0bc401..4ffbbc6 100644
--- a/tests/test_pagure_flask_api_fork_update.py
+++ b/tests/test_pagure_flask_api_fork_update.py
@@ -34,14 +34,14 @@ import tests
 
 
 class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
-    """ Tests for the flask API of pagure for updating a PR """
+    """Tests for the flask API of pagure for updating a PR"""
 
     maxDiff = None
 
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiForkUpdatetests, self).setUp()
 
         tests.create_projects(self.session)
@@ -101,8 +101,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
         self.assertEqual(output.status_code, 200)
 
     def test_api_pull_request_update_invalid_project_namespace(self):
-        """ Test api_pull_request_update method when the project doesn't exist.
-        """
+        """Test api_pull_request_update method when the project doesn't exist."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -123,8 +122,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
         )
 
     def test_api_pull_request_update_invalid_project(self):
-        """ Test api_pull_request_update method when the project doesn't exist.
-        """
+        """Test api_pull_request_update method when the project doesn't exist."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -137,7 +135,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
         )
 
     def test_api_pull_request_update_invalid_project_token(self):
-        """ Test api_pull_request_update method when the token doesn't correspond
+        """Test api_pull_request_update method when the token doesn't correspond
         to the project.
         """
 
@@ -154,8 +152,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
         )
 
     def test_api_pull_request_update_invalid_pr(self):
-        """ Test api_assign_pull_request method when asking for an invalid PR
-        """
+        """Test api_assign_pull_request method when asking for an invalid PR"""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -168,8 +165,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
         )
 
     def test_api_pull_request_update_no_input(self):
-        """ Test api_assign_pull_request method when no input is specified
-        """
+        """Test api_assign_pull_request method when no input is specified"""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -187,8 +183,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
         )
 
     def test_api_pull_request_update_insufficient_input(self):
-        """ Test api_assign_pull_request method when no input is specified
-        """
+        """Test api_assign_pull_request method when no input is specified"""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {"initial_comment": "will not work"}
@@ -209,8 +204,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
         )
 
     def test_api_pull_request_update_edited(self):
-        """ Test api_assign_pull_request method when with valid input
-        """
+        """Test api_assign_pull_request method when with valid input"""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -252,6 +246,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                 "commit_start": "5f5d609db65d447f77ba00e25afd17ba5053344b",
                 "commit_stop": "5f5d609db65d447f77ba00e25afd17ba5053344b",
                 "date_created": "1551276260",
+                "full_url": "http://localhost.localdomain/test/pull-request/1",
                 "id": 1,
                 "initial_comment": "Edited initial comment",
                 "last_updated": "1551276261",
@@ -279,6 +274,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                     "date_created": "1551276259",
                     "date_modified": "1551276259",
                     "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
                     "fullname": "test",
                     "id": 1,
                     "milestones": {},
@@ -290,6 +286,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                     "url_path": "test",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -314,6 +311,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                     "date_created": "1551276259",
                     "date_modified": "1551276259",
                     "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/fork/pingou/test",
                     "fullname": "forks/pingou/test",
                     "id": 4,
                     "milestones": {},
@@ -343,6 +341,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                         "date_created": "1551276259",
                         "date_modified": "1551276259",
                         "description": "test project #1",
+                        "full_url": "http://localhost.localdomain/test",
                         "fullname": "test",
                         "id": 1,
                         "milestones": {},
@@ -354,6 +353,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                         "url_path": "test",
                         "user": {
                             "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
                             "name": "pingou",
                             "url_path": "user/pingou",
                         },
@@ -363,6 +363,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                     "url_path": "fork/pingou/test",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -375,6 +376,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                 "updated_on": "1551276260",
                 "user": {
                     "fullname": "PY C",
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "name": "pingou",
                     "url_path": "user/pingou",
                 },
@@ -382,8 +384,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
         )
 
     def test_api_pull_request_update_edited_no_comment(self):
-        """ Test api_assign_pull_request method when with valid input
-        """
+        """Test api_assign_pull_request method when with valid input"""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -422,6 +423,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                 "commit_start": "5f5d609db65d447f77ba00e25afd17ba5053344b",
                 "commit_stop": "5f5d609db65d447f77ba00e25afd17ba5053344b",
                 "date_created": "1551276260",
+                "full_url": "http://localhost.localdomain/test/pull-request/1",
                 "id": 1,
                 "initial_comment": "",
                 "last_updated": "1551276261",
@@ -449,6 +451,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                     "date_created": "1551276259",
                     "date_modified": "1551276259",
                     "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
                     "fullname": "test",
                     "id": 1,
                     "milestones": {},
@@ -460,6 +463,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                     "url_path": "test",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -484,6 +488,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                     "date_created": "1551276259",
                     "date_modified": "1551276259",
                     "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/fork/pingou/test",
                     "fullname": "forks/pingou/test",
                     "id": 4,
                     "milestones": {},
@@ -513,6 +518,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                         "date_created": "1551276259",
                         "date_modified": "1551276259",
                         "description": "test project #1",
+                        "full_url": "http://localhost.localdomain/test",
                         "fullname": "test",
                         "id": 1,
                         "milestones": {},
@@ -524,6 +530,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                         "url_path": "test",
                         "user": {
                             "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
                             "name": "pingou",
                             "url_path": "user/pingou",
                         },
@@ -533,6 +540,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                     "url_path": "fork/pingou/test",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -545,6 +553,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                 "updated_on": "1551276260",
                 "user": {
                     "fullname": "PY C",
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "name": "pingou",
                     "url_path": "user/pingou",
                 },
@@ -552,8 +561,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
         )
 
     def test_api_pull_request_update_edited_linked(self):
-        """ Test api_assign_pull_request method when with valid input
-        """
+        """Test api_assign_pull_request method when with valid input"""
         project = pagure.lib.query.get_authorized_project(self.session, "test")
         self.assertEqual(len(project.requests), 1)
         self.assertEqual(len(project.requests[0].related_issues), 0)
@@ -611,6 +619,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                 "commit_start": "5f5d609db65d447f77ba00e25afd17ba5053344b",
                 "commit_stop": "5f5d609db65d447f77ba00e25afd17ba5053344b",
                 "date_created": "1551276260",
+                "full_url": "http://localhost.localdomain/test/pull-request/1",
                 "id": 1,
                 "initial_comment": "Edited initial comment\n\nthis PR "
                 "fixes #2 \n\nThanks",
@@ -639,6 +648,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                     "date_created": "1551276259",
                     "date_modified": "1551276259",
                     "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
                     "fullname": "test",
                     "id": 1,
                     "milestones": {},
@@ -650,6 +660,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                     "url_path": "test",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -674,6 +685,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                     "date_created": "1551276259",
                     "date_modified": "1551276259",
                     "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/fork/pingou/test",
                     "fullname": "forks/pingou/test",
                     "id": 4,
                     "milestones": {},
@@ -703,6 +715,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                         "date_created": "1551276259",
                         "date_modified": "1551276259",
                         "description": "test project #1",
+                        "full_url": "http://localhost.localdomain/test",
                         "fullname": "test",
                         "id": 1,
                         "milestones": {},
@@ -714,6 +727,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                         "url_path": "test",
                         "user": {
                             "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
                             "name": "pingou",
                             "url_path": "user/pingou",
                         },
@@ -723,6 +737,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                     "url_path": "fork/pingou/test",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -735,6 +750,7 @@ class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
                 "updated_on": "1551276260",
                 "user": {
                     "fullname": "PY C",
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "name": "pingou",
                     "url_path": "user/pingou",
                 },
diff --git a/tests/test_pagure_flask_api_group.py b/tests/test_pagure_flask_api_group.py
index 39c2b7f..8199add 100644
--- a/tests/test_pagure_flask_api_group.py
+++ b/tests/test_pagure_flask_api_group.py
@@ -26,12 +26,12 @@ import tests
 
 
 class PagureFlaskApiGroupTests(tests.SimplePagureTest):
-    """ Tests for the flask API of pagure for issue """
+    """Tests for the flask API of pagure for issue"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiGroupTests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -61,7 +61,7 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
         self.assertEqual(msg, "Group added")
 
     def test_api_groups(self):
-        """ Test the api_groups function.  """
+        """Test the api_groups function."""
 
         # Add a couple of groups so that we can list them
         item = pagure.lib.model.PagureGroup(
@@ -100,7 +100,7 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
         self.assertEqual(data["total_groups"], 1)
 
     def test_api_groups_extended(self):
-        """ Test the api_groups function.  """
+        """Test the api_groups function."""
 
         # Add a couple of groups so that we can list them
         item = pagure.lib.model.PagureGroup(
@@ -149,8 +149,8 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
 
     def test_api_view_group_authenticated(self):
         """
-            Test the api_view_group method of the flask api with an
-            authenticated user. The tested group has one member.
+        Test the api_view_group method of the flask api with an
+        authenticated user. The tested group has one member.
         """
         tests.create_tokens(self.session)
 
@@ -159,9 +159,11 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
         self.assertEqual(output.status_code, 200)
         exp = {
             "display_name": "Some Group",
+            "full_url": "http://localhost.localdomain/group/some_group",
             "description": None,
             "creator": {
                 "fullname": "PY C",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "url_path": "user/pingou",
                 "default_email": "bar@pingou.com",
                 "emails": ["bar@pingou.com", "foo@pingou.com"],
@@ -178,16 +180,18 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
 
     def test_api_view_group_unauthenticated(self):
         """
-            Test the api_view_group method of the flask api with an
-            unauthenticated user. The tested group has one member.
+        Test the api_view_group method of the flask api with an
+        unauthenticated user. The tested group has one member.
         """
         output = self.app.get("/api/0/group/some_group")
         self.assertEqual(output.status_code, 200)
         exp = {
             "display_name": "Some Group",
+            "full_url": "http://localhost.localdomain/group/some_group",
             "description": None,
             "creator": {
                 "fullname": "PY C",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "name": "pingou",
                 "url_path": "user/pingou",
             },
@@ -202,8 +206,8 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
 
     def test_api_view_group_two_members_authenticated(self):
         """
-            Test the api_view_group method of the flask api with an
-            authenticated user. The tested group has two members.
+        Test the api_view_group method of the flask api with an
+        authenticated user. The tested group has two members.
         """
         user = pagure.lib.model.User(
             user="mprahl",
@@ -231,9 +235,11 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
         self.assertEqual(output.status_code, 200)
         exp = {
             "display_name": "Some Group",
+            "full_url": "http://localhost.localdomain/group/some_group",
             "description": None,
             "creator": {
                 "fullname": "PY C",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "default_email": "bar@pingou.com",
                 "emails": ["bar@pingou.com", "foo@pingou.com"],
                 "name": "pingou",
@@ -251,8 +257,8 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
 
     def test_api_view_group_no_group_error(self):
         """
-            Test the api_view_group method of the flask api
-            The tested group has one member.
+        Test the api_view_group method of the flask api
+        The tested group has one member.
         """
         output = self.app.get("/api/0/group/some_group3")
         self.assertEqual(output.status_code, 404)
@@ -262,8 +268,8 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
 
     def test_api_view_group_w_projects_and_acl(self):
         """
-            Test the api_view_group method with project info and restricted
-            to the admin ACL
+        Test the api_view_group method with project info and restricted
+        to the admin ACL
         """
         tests.create_tokens(self.session)
 
@@ -274,9 +280,11 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
         self.assertEqual(output.status_code, 200)
         exp = {
             "display_name": "Some Group",
+            "full_url": "http://localhost.localdomain/group/some_group",
             "description": None,
             "creator": {
                 "fullname": "PY C",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "default_email": "bar@pingou.com",
                 "emails": ["bar@pingou.com", "foo@pingou.com"],
                 "name": "pingou",
@@ -286,6 +294,15 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
             "date_created": "1492020239",
             "group_type": "user",
             "name": "some_group",
+            "pagination": {
+                "first": "http://localhost...",
+                "last": "http://localhost...",
+                "next": None,
+                "page": 1,
+                "pages": 1,
+                "per_page": 20,
+                "prev": None,
+            },
             "projects": [
                 {
                     "access_groups": {
@@ -311,6 +328,7 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
                     "date_created": "1492020239",
                     "date_modified": "1492020239",
                     "description": "test project #2",
+                    "full_url": "http://localhost.localdomain/test2",
                     "fullname": "test2",
                     "id": 2,
                     "milestones": {},
@@ -322,14 +340,20 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
                     "url_path": "test2",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
                 }
             ],
+            "total_projects": 1,
         }
         data = json.loads(output.get_data(as_text=True))
         data["date_created"] = "1492020239"
+        self.assertIsNotNone(data["pagination"]["first"])
+        data["pagination"]["first"] = "http://localhost..."
+        self.assertIsNotNone(data["pagination"]["last"])
+        data["pagination"]["last"] = "http://localhost..."
         projects = []
         for p in data["projects"]:
             p["date_created"] = "1492020239"
@@ -338,27 +362,38 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
         data["projects"] = projects
         self.assertDictEqual(data, exp)
 
-        output2 = self.app.get(
+        output = self.app.get(
             "/api/0/group/some_group?projects=1&acl=admin", headers=headers
         )
-        self.assertListEqual(
-            output.get_data(as_text=True).split("\n"),
-            output2.get_data(as_text=True).split("\n"),
-        )
+        data = json.loads(output.get_data(as_text=True))
+        data["date_created"] = "1492020239"
+        self.assertIsNotNone(data["pagination"]["first"])
+        data["pagination"]["first"] = "http://localhost..."
+        self.assertIsNotNone(data["pagination"]["last"])
+        data["pagination"]["last"] = "http://localhost..."
+        projects = []
+        for p in data["projects"]:
+            p["date_created"] = "1492020239"
+            p["date_modified"] = "1492020239"
+            projects.append(p)
+        data["projects"] = projects
+        self.assertDictEqual(data, exp)
 
     def test_api_view_group_w_projects_and_acl_commit(self):
         """
-            Test the api_view_group method with project info and restricted
-            to the commit ACL
+        Test the api_view_group method with project info and restricted
+        to the commit ACL
         """
 
         output = self.app.get("/api/0/group/some_group?projects=1&acl=commit")
         self.assertEqual(output.status_code, 200)
         exp = {
             "display_name": "Some Group",
+            "full_url": "http://localhost.localdomain/group/some_group",
             "description": None,
             "creator": {
                 "fullname": "PY C",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "name": "pingou",
                 "url_path": "user/pingou",
             },
@@ -366,6 +401,15 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
             "date_created": "1492020239",
             "group_type": "user",
             "name": "some_group",
+            "pagination": {
+                "first": "http://localhost...",
+                "last": "http://localhost...",
+                "next": None,
+                "page": 1,
+                "pages": 1,
+                "per_page": 20,
+                "prev": None,
+            },
             "projects": [
                 {
                     "access_groups": {
@@ -391,6 +435,7 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
                     "date_created": "1492020239",
                     "date_modified": "1492020239",
                     "description": "test project #2",
+                    "full_url": "http://localhost.localdomain/test2",
                     "fullname": "test2",
                     "id": 2,
                     "milestones": {},
@@ -402,14 +447,20 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
                     "url_path": "test2",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
                 }
             ],
+            "total_projects": 1,
         }
         data = json.loads(output.get_data(as_text=True))
         data["date_created"] = "1492020239"
+        self.assertIsNotNone(data["pagination"]["first"])
+        data["pagination"]["first"] = "http://localhost..."
+        self.assertIsNotNone(data["pagination"]["last"])
+        data["pagination"]["last"] = "http://localhost..."
         projects = []
         for p in data["projects"]:
             p["date_created"] = "1492020239"
@@ -420,17 +471,19 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
 
     def test_api_view_group_w_projects_and_acl_ticket(self):
         """
-            Test the api_view_group method with project info and restricted
-            to the ticket ACL
+        Test the api_view_group method with project info and restricted
+        to the ticket ACL
         """
 
         output = self.app.get("/api/0/group/some_group?projects=1&acl=ticket")
         self.assertEqual(output.status_code, 200)
         exp = {
             "display_name": "Some Group",
+            "full_url": "http://localhost.localdomain/group/some_group",
             "description": None,
             "creator": {
                 "fullname": "PY C",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "name": "pingou",
                 "url_path": "user/pingou",
             },
@@ -438,6 +491,15 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
             "date_created": "1492020239",
             "group_type": "user",
             "name": "some_group",
+            "pagination": {
+                "first": "http://localhost...",
+                "last": "http://localhost...",
+                "next": None,
+                "page": 1,
+                "pages": 1,
+                "per_page": 20,
+                "prev": None,
+            },
             "projects": [
                 {
                     "access_groups": {
@@ -463,6 +525,7 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
                     "date_created": "1492020239",
                     "date_modified": "1492020239",
                     "description": "test project #2",
+                    "full_url": "http://localhost.localdomain/test2",
                     "fullname": "test2",
                     "id": 2,
                     "milestones": {},
@@ -474,14 +537,20 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
                     "url_path": "test2",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
                 }
             ],
+            "total_projects": 1,
         }
         data = json.loads(output.get_data(as_text=True))
         data["date_created"] = "1492020239"
+        self.assertIsNotNone(data["pagination"]["first"])
+        data["pagination"]["first"] = "http://localhost..."
+        self.assertIsNotNone(data["pagination"]["last"])
+        data["pagination"]["last"] = "http://localhost..."
         projects = []
         for p in data["projects"]:
             p["date_created"] = "1492020239"
@@ -492,8 +561,8 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
 
     def test_api_view_group_w_projects_and_acl_admin_no_project(self):
         """
-            Test the api_view_group method with project info and restricted
-            to the admin ACL
+        Test the api_view_group method with project info and restricted
+        to the admin ACL
         """
 
         # Make the group having only commit access
@@ -512,9 +581,11 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
         self.assertEqual(output.status_code, 200)
         exp = {
             "display_name": "Some Group",
+            "full_url": "http://localhost.localdomain/group/some_group",
             "description": None,
             "creator": {
                 "fullname": "PY C",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "name": "pingou",
                 "url_path": "user/pingou",
             },
@@ -522,16 +593,30 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
             "date_created": "1492020239",
             "group_type": "user",
             "name": "some_group",
+            "pagination": {
+                "first": "http://localhost...",
+                "last": "http://localhost...",
+                "next": None,
+                "page": 1,
+                "pages": 0,
+                "per_page": 20,
+                "prev": None,
+            },
             "projects": [],
+            "total_projects": 0,
         }
         data = json.loads(output.get_data(as_text=True))
         data["date_created"] = "1492020239"
+        self.assertIsNotNone(data["pagination"]["first"])
+        data["pagination"]["first"] = "http://localhost..."
+        self.assertIsNotNone(data["pagination"]["last"])
+        data["pagination"]["last"] = "http://localhost..."
         self.assertDictEqual(data, exp)
 
     def test_api_view_group_w_projects_and_acl_commit_no_project(self):
         """
-            Test the api_view_group method with project info and restricted
-            to the commit ACL
+        Test the api_view_group method with project info and restricted
+        to the commit ACL
         """
 
         # Make the group having only ticket access
@@ -550,9 +635,11 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
         self.assertEqual(output.status_code, 200)
         exp = {
             "display_name": "Some Group",
+            "full_url": "http://localhost.localdomain/group/some_group",
             "description": None,
             "creator": {
                 "fullname": "PY C",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "name": "pingou",
                 "url_path": "user/pingou",
             },
@@ -560,16 +647,30 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
             "date_created": "1492020239",
             "group_type": "user",
             "name": "some_group",
+            "pagination": {
+                "first": "http://localhost...",
+                "last": "http://localhost...",
+                "next": None,
+                "page": 1,
+                "pages": 0,
+                "per_page": 20,
+                "prev": None,
+            },
             "projects": [],
+            "total_projects": 0,
         }
         data = json.loads(output.get_data(as_text=True))
         data["date_created"] = "1492020239"
+        self.assertIsNotNone(data["pagination"]["first"])
+        data["pagination"]["first"] = "http://localhost..."
+        self.assertIsNotNone(data["pagination"]["last"])
+        data["pagination"]["last"] = "http://localhost..."
         self.assertDictEqual(data, exp)
 
     def test_api_view_group_w_projects_and_acl_ticket_no_project(self):
         """
-            Test the api_view_group method with project info and restricted
-            to the ticket ACL
+        Test the api_view_group method with project info and restricted
+        to the ticket ACL
         """
 
         # Create a group not linked to any project
@@ -586,9 +687,11 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
         self.assertEqual(output.status_code, 200)
         exp = {
             "display_name": "Release engineering group",
+            "full_url": "http://localhost.localdomain/group/rel-eng",
             "description": None,
             "creator": {
                 "fullname": "PY C",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "name": "pingou",
                 "url_path": "user/pingou",
             },
@@ -596,12 +699,80 @@ class PagureFlaskApiGroupTests(tests.SimplePagureTest):
             "date_created": "1492020239",
             "group_type": "user",
             "name": "rel-eng",
+            "pagination": {
+                "first": "http://localhost...",
+                "last": "http://localhost...",
+                "next": None,
+                "page": 1,
+                "pages": 0,
+                "per_page": 20,
+                "prev": None,
+            },
             "projects": [],
+            "total_projects": 0,
         }
         data = json.loads(output.get_data(as_text=True))
         data["date_created"] = "1492020239"
+        self.assertIsNotNone(data["pagination"]["first"])
+        data["pagination"]["first"] = "http://localhost..."
+        self.assertIsNotNone(data["pagination"]["last"])
+        data["pagination"]["last"] = "http://localhost..."
         self.assertDictEqual(data, exp)
 
+    def test_api_view_group_w_projects_and_acl_pagination(self):
+        """
+        Tests the pagination for the api_view_group method
+        """
+
+        project = pagure.lib.query._get_project(self.session, "test2")
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="some_group",
+            user="pingou",
+            access="commit",
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group access updated")
+
+        project_another = pagure.lib.query._get_project(self.session, "test")
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project_another,
+            new_group="some_group",
+            user="pingou",
+            access="commit",
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        tests.create_tokens(self.session)
+
+        headers = {"Authorization": "token aaabbbcccddd"}
+        output = self.app.get(
+            "/api/0/group/some_group?per_page=1&projects=1", headers=headers
+        )
+        self.assertEqual(output.status_code, 200)
+
+        data = json.loads(output.get_data(as_text=True))
+        projects = [project["name"] for project in data["projects"]]
+
+        # Test the result we've got from the first page out of two
+        assert projects == ["test"]
+
+        output_last = self.app.get(
+            data["pagination"]["next"].replace("http://localhost", ""),
+            headers=headers,
+        )
+        self.assertEqual(output_last.status_code, 200)
+        data_last = json.loads(output_last.get_data(as_text=True))
+
+        projects.extend([project["name"] for project in data_last["projects"]])
+
+        # Note that pagure sorts projects alphabetically, so we're comparing
+        # a different order that was the order of requests
+        assert projects == ["test", "test2"]
+
 
 if __name__ == "__main__":
     unittest.main(verbosity=2)
diff --git a/tests/test_pagure_flask_api_issue.py b/tests/test_pagure_flask_api_issue.py
index 5b04265..fc4a056 100644
--- a/tests/test_pagure_flask_api_issue.py
+++ b/tests/test_pagure_flask_api_issue.py
@@ -20,9 +20,12 @@ import time
 import os
 
 import flask
+import pagure_messages
 import json
 import munch
-from mock import patch, MagicMock
+
+from fedora_messaging import api, testing
+from mock import ANY, patch, MagicMock
 from sqlalchemy.exc import SQLAlchemyError
 
 sys.path.insert(
@@ -43,6 +46,7 @@ FULL_ISSUE_LIST = [
         "comments": [],
         "content": "We should work on this",
         "custom_fields": [],
+        "full_url": "http://localhost.localdomain/test/issue/2",
         "date_created": "1431414800",
         "depends": [],
         "id": 2,
@@ -56,6 +60,7 @@ FULL_ISSUE_LIST = [
         "title": "Test issue",
         "user": {
             "fullname": "PY C",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "name": "pingou",
             "url_path": "user/pingou",
         },
@@ -63,6 +68,7 @@ FULL_ISSUE_LIST = [
     {
         "assignee": {
             "fullname": "foo bar",
+            "full_url": "http://localhost.localdomain/user/foo",
             "name": "foo",
             "url_path": "user/foo",
         },
@@ -73,6 +79,7 @@ FULL_ISSUE_LIST = [
         "comments": [],
         "content": "This issue needs attention",
         "custom_fields": [],
+        "full_url": "http://localhost.localdomain/test/issue/8",
         "date_created": "1431414800",
         "depends": [],
         "id": 8,
@@ -86,6 +93,7 @@ FULL_ISSUE_LIST = [
         "title": "test issue1",
         "user": {
             "fullname": "PY C",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "name": "pingou",
             "url_path": "user/pingou",
         },
@@ -99,6 +107,7 @@ FULL_ISSUE_LIST = [
         "comments": [],
         "content": "This issue needs attention",
         "custom_fields": [],
+        "full_url": "http://localhost.localdomain/test/issue/7",
         "date_created": "1431414800",
         "depends": [],
         "id": 7,
@@ -112,6 +121,7 @@ FULL_ISSUE_LIST = [
         "title": "test issue",
         "user": {
             "fullname": "PY C",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "name": "pingou",
             "url_path": "user/pingou",
         },
@@ -125,6 +135,7 @@ FULL_ISSUE_LIST = [
         "comments": [],
         "content": "This issue needs attention",
         "custom_fields": [],
+        "full_url": "http://localhost.localdomain/test/issue/6",
         "date_created": "1431414800",
         "depends": [],
         "id": 6,
@@ -138,6 +149,7 @@ FULL_ISSUE_LIST = [
         "title": "test issue",
         "user": {
             "fullname": "PY C",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "name": "pingou",
             "url_path": "user/pingou",
         },
@@ -151,6 +163,7 @@ FULL_ISSUE_LIST = [
         "comments": [],
         "content": "This issue needs attention",
         "custom_fields": [],
+        "full_url": "http://localhost.localdomain/test/issue/5",
         "date_created": "1431414800",
         "depends": [],
         "id": 5,
@@ -164,6 +177,7 @@ FULL_ISSUE_LIST = [
         "title": "test issue",
         "user": {
             "fullname": "PY C",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "name": "pingou",
             "url_path": "user/pingou",
         },
@@ -177,6 +191,7 @@ FULL_ISSUE_LIST = [
         "comments": [],
         "content": "This issue needs attention",
         "custom_fields": [],
+        "full_url": "http://localhost.localdomain/test/issue/4",
         "date_created": "1431414800",
         "depends": [],
         "id": 4,
@@ -190,6 +205,7 @@ FULL_ISSUE_LIST = [
         "title": "test issue",
         "user": {
             "fullname": "PY C",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "name": "pingou",
             "url_path": "user/pingou",
         },
@@ -203,6 +219,7 @@ FULL_ISSUE_LIST = [
         "comments": [],
         "content": "This issue needs attention",
         "custom_fields": [],
+        "full_url": "http://localhost.localdomain/test/issue/3",
         "date_created": "1431414800",
         "depends": [],
         "id": 3,
@@ -216,6 +233,7 @@ FULL_ISSUE_LIST = [
         "title": "test issue",
         "user": {
             "fullname": "PY C",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "name": "pingou",
             "url_path": "user/pingou",
         },
@@ -229,6 +247,7 @@ FULL_ISSUE_LIST = [
         "comments": [],
         "content": "This issue needs attention",
         "custom_fields": [],
+        "full_url": "http://localhost.localdomain/test/issue/2",
         "date_created": "1431414800",
         "depends": [],
         "id": 2,
@@ -242,6 +261,7 @@ FULL_ISSUE_LIST = [
         "title": "test issue",
         "user": {
             "fullname": "PY C",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "name": "pingou",
             "url_path": "user/pingou",
         },
@@ -255,6 +275,7 @@ FULL_ISSUE_LIST = [
         "comments": [],
         "content": "This issue needs attention",
         "custom_fields": [],
+        "full_url": "http://localhost.localdomain/test/issue/1",
         "date_created": "1431414800",
         "depends": [],
         "id": 1,
@@ -268,6 +289,7 @@ FULL_ISSUE_LIST = [
         "title": "test issue",
         "user": {
             "fullname": "PY C",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "name": "pingou",
             "url_path": "user/pingou",
         },
@@ -285,6 +307,7 @@ LCL_ISSUES = [
         "comments": [],
         "content": "Description",
         "custom_fields": [],
+        "full_url": "http://localhost.localdomain/test/issue/2",
         "date_created": "1431414800",
         "depends": [],
         "id": 2,
@@ -298,6 +321,7 @@ LCL_ISSUES = [
         "title": "Issue #2",
         "user": {
             "fullname": "PY C",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "name": "pingou",
             "url_path": "user/pingou",
         },
@@ -311,6 +335,7 @@ LCL_ISSUES = [
         "comments": [],
         "content": "Description",
         "custom_fields": [],
+        "full_url": "http://localhost.localdomain/test/issue/1",
         "date_created": "1431414800",
         "depends": [],
         "id": 1,
@@ -324,6 +349,7 @@ LCL_ISSUES = [
         "title": "Issue #1",
         "user": {
             "fullname": "PY C",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "name": "pingou",
             "url_path": "user/pingou",
         },
@@ -332,17 +358,17 @@ LCL_ISSUES = [
 
 
 class PagureFlaskApiIssuetests(tests.SimplePagureTest):
-    """ Tests for the flask API of pagure for issue """
+    """Tests for the flask API of pagure for issue"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiIssuetests, self).setUp()
         pagure.config.config["TICKETS_FOLDER"] = None
 
     def test_api_new_issue_wrong_token(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -366,7 +392,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         "pagure.config.config", {"ENABLE_TICKETS_NAMESPACE": ["foobar"]}
     )
     def test_api_new_issue_wrong_namespace(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -391,7 +417,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_new_issue_no_input(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -418,7 +444,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_new_issue_invalid_repo(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -441,7 +467,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_new_issue_invalid_request(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -469,8 +495,11 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
             },
         )
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     def test_api_new_issue(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -486,9 +515,86 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         }
 
         # Valid request
-        output = self.app.post(
-            "/api/0/test/new_issue", data=data, headers=headers
-        )
+        with testing.mock_sends(
+            pagure_messages.IssueNewV1(
+                topic="pagure.issue.new",
+                body={
+                    "issue": {
+                        "id": 1,
+                        "title": "test issue",
+                        "content": "This issue needs attention",
+                        "status": "Open",
+                        "close_status": None,
+                        "date_created": ANY,
+                        "last_updated": ANY,
+                        "closed_at": None,
+                        "user": {
+                            "name": "pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                        },
+                        "private": False,
+                        "tags": [],
+                        "depends": [],
+                        "blocks": [],
+                        "assignee": None,
+                        "priority": None,
+                        "milestone": None,
+                        "custom_fields": [],
+                        "full_url": "http://localhost.localdomain/test/issue/1",
+                        "closed_by": None,
+                        "related_prs": [],
+                        "comments": [],
+                    },
+                    "project": {
+                        "id": 1,
+                        "name": "test",
+                        "fullname": "test",
+                        "url_path": "test",
+                        "description": "test project #1",
+                        "full_url": "http://localhost.localdomain/test",
+                        "namespace": None,
+                        "parent": None,
+                        "date_created": ANY,
+                        "date_modified": ANY,
+                        "user": {
+                            "name": "pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                        },
+                        "access_users": {
+                            "owner": ["pingou"],
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "access_groups": {
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "tags": [],
+                        "priorities": {},
+                        "custom_keys": [],
+                        "close_status": [
+                            "Invalid",
+                            "Insufficient data",
+                            "Fixed",
+                            "Duplicate",
+                        ],
+                        "milestones": {},
+                    },
+                    "agent": "pingou",
+                },
+            )
+        ):
+            output = self.app.post(
+                "/api/0/test/new_issue", data=data, headers=headers
+            )
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
         data["issue"]["date_created"] = "1431414800"
@@ -498,7 +604,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_new_issue_img(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -539,7 +645,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
             )
 
     def test_api_new_issue_invalid_milestone(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -570,7 +676,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_new_issue_milestone(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -602,13 +708,14 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         issue = copy.deepcopy(FULL_ISSUE_LIST[7])
         issue["id"] = 1
+        issue["full_url"] = "http://localhost.localdomain/test/issue/1"
 
         self.assertDictEqual(
             data, {"issue": issue, "message": "Issue created"}
         )
 
     def test_api_new_issue_public(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -635,6 +742,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         issue = copy.deepcopy(FULL_ISSUE_LIST[6])
         issue["id"] = 1
+        issue["full_url"] = "http://localhost.localdomain/test/issue/1"
 
         self.assertDictEqual(
             data, {"issue": issue, "message": "Issue created"}
@@ -657,6 +765,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         issue = copy.deepcopy(FULL_ISSUE_LIST[5])
         issue["id"] = 2
+        issue["full_url"] = "http://localhost.localdomain/test/issue/2"
 
         self.assertDictEqual(
             data, {"issue": issue, "message": "Issue created"}
@@ -679,6 +788,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         issue = copy.deepcopy(FULL_ISSUE_LIST[4])
         issue["id"] = 3
+        issue["full_url"] = "http://localhost.localdomain/test/issue/3"
 
         self.assertDictEqual(
             data, {"issue": issue, "message": "Issue created"}
@@ -701,13 +811,14 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         issue = copy.deepcopy(FULL_ISSUE_LIST[3])
         issue["id"] = 4
+        issue["full_url"] = "http://localhost.localdomain/test/issue/4"
 
         self.assertDictEqual(
             data, {"issue": issue, "message": "Issue created"}
         )
 
     def test_api_new_issue_private(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -733,6 +844,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         issue = copy.deepcopy(FULL_ISSUE_LIST[2])
         issue["id"] = 1
+        issue["full_url"] = "http://localhost.localdomain/test/issue/1"
 
         self.assertDictEqual(
             data, {"issue": issue, "message": "Issue created"}
@@ -755,12 +867,66 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         exp = copy.deepcopy(FULL_ISSUE_LIST[1])
         exp["id"] = 2
+        exp["full_url"] = "http://localhost.localdomain/test/issue/2"
 
         self.assertDictEqual(data, {"issue": exp, "message": "Issue created"})
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
+    def test_api_new_issue_private_no_fedora_messaging_notifs(self):
+        """Test the api_new_issue method of the flask api."""
+        tests.create_projects(self.session)
+        tests.create_projects_git(
+            os.path.join(self.path, "tickets"), bare=True
+        )
+        tests.create_tokens(self.session)
+        tests.create_tokens_acl(self.session)
+
+        headers = {"Authorization": "token aaabbbcccddd"}
+
+        # Private issue: True
+        data = {
+            "title": "test issue",
+            "issue_content": "This issue needs attention",
+            "private": True,
+        }
+        output = self.app.post(
+            "/api/0/test/new_issue", data=data, headers=headers
+        )
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        data["issue"]["date_created"] = "1431414800"
+        data["issue"]["last_updated"] = "1431414800"
+
+        issue = copy.deepcopy(FULL_ISSUE_LIST[2])
+        issue["id"] = 1
+        issue["full_url"] = "http://localhost.localdomain/test/issue/1"
+
+        self.assertDictEqual(
+            data, {"issue": issue, "message": "Issue created"}
+        )
+
+        # Private issue: 1
+        data = {
+            "title": "test issue1",
+            "issue_content": "This issue needs attention",
+            "private": 1,
+            "assignee": "foo",
+        }
+        with self.assertRaises(AssertionError) as cm:
+            with testing.mock_sends(api.Message()):
+                output = self.app.post(
+                    "/api/0/test/new_issue", data=data, headers=headers
+                )
+        self.assertEqual(
+            cm.exception.args[0],
+            "Expected 1 messages to be sent, but 0 were sent",
+        )
+
     @patch("pagure.utils.check_api_acls", MagicMock(return_value=None))
     def test_api_new_issue_raise_db_error(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -799,7 +965,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
                 )
 
     def test_api_new_issue_user_token_no_input(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -826,7 +992,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_new_issue_user_token_invalid_user(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -853,7 +1019,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_new_issue_user_token_invalid_repo(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -875,7 +1041,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_new_issue_user_token_invalid_request(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -903,7 +1069,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_new_issue_user_token(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -931,7 +1097,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_new_issue_user_token_milestone(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -963,13 +1129,14 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         issue = copy.deepcopy(FULL_ISSUE_LIST[7])
         issue["id"] = 1
+        issue["full_url"] = "http://localhost.localdomain/test/issue/1"
 
         self.assertDictEqual(
             data, {"issue": issue, "message": "Issue created"}
         )
 
     def test_api_new_issue_user_token_public(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -996,6 +1163,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         issue = copy.deepcopy(FULL_ISSUE_LIST[6])
         issue["id"] = 1
+        issue["full_url"] = "http://localhost.localdomain/test/issue/1"
 
         self.assertDictEqual(
             data, {"issue": issue, "message": "Issue created"}
@@ -1018,6 +1186,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         issue = copy.deepcopy(FULL_ISSUE_LIST[5])
         issue["id"] = 2
+        issue["full_url"] = "http://localhost.localdomain/test/issue/2"
 
         self.assertDictEqual(
             data, {"issue": issue, "message": "Issue created"}
@@ -1040,6 +1209,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         issue = copy.deepcopy(FULL_ISSUE_LIST[4])
         issue["id"] = 3
+        issue["full_url"] = "http://localhost.localdomain/test/issue/3"
 
         self.assertDictEqual(
             data, {"issue": issue, "message": "Issue created"}
@@ -1062,13 +1232,14 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         issue = copy.deepcopy(FULL_ISSUE_LIST[4])
         issue["id"] = 4
+        issue["full_url"] = "http://localhost.localdomain/test/issue/4"
 
         self.assertDictEqual(
             data, {"issue": issue, "message": "Issue created"}
         )
 
     def test_api_new_issue_user_token_private(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -1094,6 +1265,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         issue = copy.deepcopy(FULL_ISSUE_LIST[2])
         issue["id"] = 1
+        issue["full_url"] = "http://localhost.localdomain/test/issue/1"
 
         self.assertDictEqual(
             data, {"issue": issue, "message": "Issue created"}
@@ -1116,6 +1288,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         issue = copy.deepcopy(FULL_ISSUE_LIST[1])
         issue["id"] = 2
+        issue["full_url"] = "http://localhost.localdomain/test/issue/2"
 
         self.assertDictEqual(
             data, {"issue": issue, "message": "Issue created"}
@@ -1137,12 +1310,13 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
 
         exp = copy.deepcopy(FULL_ISSUE_LIST[1])
         exp["id"] = 3
+        exp["full_url"] = "http://localhost.localdomain/test/issue/3"
         exp["assignee"] = None
 
         self.assertDictEqual(data, {"issue": exp, "message": "Issue created"})
 
     def test_api_view_issues(self):
-        """ Test the api_view_issues method of the flask api. """
+        """Test the api_view_issues method of the flask api."""
         self.test_api_new_issue()
 
         # Invalid repo
@@ -1559,7 +1733,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_view_issues_user_token(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -1626,7 +1800,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_view_issues_private_user_token(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets"), bare=True
@@ -1735,7 +1909,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_view_issues_since_invalid_format(self):
-        """ Test the api_view_issues method of the flask api. """
+        """Test the api_view_issues method of the flask api."""
         self.test_api_new_issue()
 
         # Invalid repo
@@ -1748,7 +1922,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_view_issues_since_invalid_timestamp(self):
-        """ Test the api_view_issues method of the flask api. """
+        """Test the api_view_issues method of the flask api."""
         self.test_api_new_issue()
 
         # Invalid repo
@@ -1763,7 +1937,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_view_issues_reversed(self):
-        """ Test the api_view_issues method of the flask api. in reversed
+        """Test the api_view_issues method of the flask api. in reversed
         order.
 
         """
@@ -1809,7 +1983,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         self.assertDictEqual(data, expected)
 
     def test_api_view_issues_milestone(self):
-        """ Test the api_view_issues method of the flask api when filtering
+        """Test the api_view_issues method of the flask api when filtering
         for a milestone.
         """
         tests.create_projects(self.session)
@@ -1929,7 +2103,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_view_issues_priority(self):
-        """ Test the api_view_issues method of the flask api when filtering
+        """Test the api_view_issues method of the flask api when filtering
         for a priority.
         """
         tests.create_projects(self.session)
@@ -2092,7 +2266,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_view_issues_priority_invalid(self):
-        """ Test the api_view_issues method of the flask api when filtering
+        """Test the api_view_issues method of the flask api when filtering
         for an invalid priority.
         """
         tests.create_projects(self.session)
@@ -2115,7 +2289,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_view_issues_no_stones(self):
-        """ Test the api_view_issues method of the flask api when filtering
+        """Test the api_view_issues method of the flask api when filtering
         with no_stones.
         """
         tests.create_projects(self.session)
@@ -2274,7 +2448,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_view_issues_since(self):
-        """ Test the api_view_issues method of the flask api for since option """
+        """Test the api_view_issues method of the flask api for since option"""
 
         tests.create_projects(self.session)
         tests.create_projects_git(
@@ -2286,7 +2460,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
 
         # Create 1st tickets
-        start = arrow.utcnow().timestamp
+        start = int(arrow.utcnow().float_timestamp)
         issue = pagure.lib.model.Issue(
             id=pagure.lib.query.get_next_id(self.session, repo.id),
             project_id=repo.id,
@@ -2300,7 +2474,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         self.session.commit()
 
         time.sleep(1)
-        middle = arrow.utcnow().timestamp
+        middle = int(arrow.utcnow().float_timestamp)
 
         # Create 2nd tickets
         issue = pagure.lib.model.Issue(
@@ -2316,7 +2490,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         self.session.commit()
 
         time.sleep(1)
-        final = arrow.utcnow().timestamp
+        final = int(arrow.utcnow().float_timestamp)
 
         # Create private issue
         issue = pagure.lib.model.Issue(
@@ -2379,7 +2553,6 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
         time.sleep(1)
-        late = arrow.utcnow().timestamp
 
         # List all opened issues from the start
         output = self.app.get("/api/0/test/issues?since=%s" % start)
@@ -2539,6 +2712,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
                         "custom_fields": [],
                         "date_created": "1431414800",
                         "depends": [],
+                        "full_url": "http://localhost.localdomain/test/issue/3",
                         "id": 3,
                         "last_updated": "1431414800",
                         "milestone": None,
@@ -2550,6 +2724,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
                         "title": "Issue #3",
                         "user": {
                             "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
                             "name": "pingou",
                             "url_path": "user/pingou",
                         },
@@ -2569,7 +2744,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_view_issue(self):
-        """ Test the api_view_issue method of the flask api. """
+        """Test the api_view_issue method of the flask api."""
         self.test_api_new_issue()
 
         # Invalid repo
@@ -2602,6 +2777,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
                 "comments": [],
                 "content": "This issue needs attention",
                 "custom_fields": [],
+                "full_url": "http://localhost.localdomain/test/issue/1",
                 "date_created": "1431414800",
                 "close_status": None,
                 "closed_at": None,
@@ -2618,6 +2794,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
                 "title": "test issue",
                 "user": {
                     "fullname": "PY C",
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "name": "pingou",
                     "url_path": "user/pingou",
                 },
@@ -2706,6 +2883,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
                 "comments": [],
                 "content": "We should work on this",
                 "custom_fields": [],
+                "full_url": "http://localhost.localdomain/test/issue/2",
                 "date_created": "1431414800",
                 "close_status": None,
                 "closed_at": None,
@@ -2722,6 +2900,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
                 "title": "Test issue",
                 "user": {
                     "fullname": "PY C",
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "name": "pingou",
                     "url_path": "user/pingou",
                 },
@@ -2742,6 +2921,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
                 "comments": [],
                 "content": "We should work on this",
                 "custom_fields": [],
+                "full_url": "http://localhost.localdomain/test/issue/2",
                 "date_created": "1431414800",
                 "close_status": None,
                 "closed_at": None,
@@ -2758,6 +2938,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
                 "title": "Test issue",
                 "user": {
                     "fullname": "PY C",
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "name": "pingou",
                     "url_path": "user/pingou",
                 },
@@ -2765,7 +2946,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_change_milestone_issue_invalid_project(self):
-        """ Test the api_change_milestone_issue method of the flask api. """
+        """Test the api_change_milestone_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -2791,7 +2972,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         "pagure.config.config", {"ENABLE_TICKETS_NAMESPACE": ["foobar"]}
     )
     def test_api_change_milestone_issue_wrong_namespace(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -2835,7 +3016,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_change_milestone_issue_wrong_token(self):
-        """ Test the api_change_milestone_issue method of the flask api. """
+        """Test the api_change_milestone_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -2862,7 +3043,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_change_milestone_issue_no_issue(self):
-        """ Test the api_change_milestone_issue method of the flask api. """
+        """Test the api_change_milestone_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -2887,7 +3068,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_change_milestone_issue_no_milestone(self):
-        """ Test the api_change_milestone_issue method of the flask api. """
+        """Test the api_change_milestone_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -2934,7 +3115,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         self.assertEqual(issue.milestone, None)
 
     def test_api_change_milestone_issue_invalid_milestone(self):
-        """ Test the api_change_milestone_issue method of the flask api. """
+        """Test the api_change_milestone_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -2982,8 +3163,11 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
             },
         )
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     def test_api_change_milestone_issue(self):
-        """ Test the api_change_milestone_issue method of the flask api. """
+        """Test the api_change_milestone_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -3017,9 +3201,90 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         data = {"milestone": "v1.0"}
 
         # Valid requests
-        output = self.app.post(
-            "/api/0/test/issue/1/milestone", data=data, headers=headers
-        )
+        with testing.mock_sends(
+            pagure_messages.IssueEditV1(
+                topic="pagure.issue.edit",
+                body={
+                    "issue": {
+                        "id": 1,
+                        "title": "Test issue #1",
+                        "content": "We should work on this",
+                        "status": "Open",
+                        "close_status": None,
+                        "date_created": ANY,
+                        "last_updated": ANY,
+                        "closed_at": None,
+                        "user": {
+                            "name": "pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                        },
+                        "private": False,
+                        "tags": [],
+                        "depends": [],
+                        "blocks": [],
+                        "assignee": None,
+                        "priority": None,
+                        "milestone": "v1.0",
+                        "custom_fields": [],
+                        "full_url": "http://localhost.localdomain/test/issue/1",
+                        "closed_by": None,
+                        "related_prs": [],
+                        "comments": [],
+                    },
+                    "project": {
+                        "id": 1,
+                        "name": "test",
+                        "fullname": "test",
+                        "url_path": "test",
+                        "description": "test project #1",
+                        "full_url": "http://localhost.localdomain/test",
+                        "namespace": None,
+                        "parent": None,
+                        "date_created": ANY,
+                        "date_modified": ANY,
+                        "user": {
+                            "name": "pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                        },
+                        "access_users": {
+                            "owner": ["pingou"],
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "access_groups": {
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "tags": [],
+                        "priorities": {},
+                        "custom_keys": [],
+                        "close_status": [
+                            "Invalid",
+                            "Insufficient data",
+                            "Fixed",
+                            "Duplicate",
+                        ],
+                        "milestones": {
+                            "v1.0": {"date": None, "active": True},
+                            "v2.0": {"date": "Soon", "active": True},
+                        },
+                    },
+                    "fields": ["milestone"],
+                    "agent": "pingou",
+                },
+            )
+        ):
+            output = self.app.post(
+                "/api/0/test/issue/1/milestone", data=data, headers=headers
+            )
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
         self.assertDictEqual(
@@ -3027,7 +3292,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_change_milestone_issue_remove_milestone(self):
-        """ Test the api_change_milestone_issue method of the flask api. """
+        """Test the api_change_milestone_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -3089,7 +3354,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         self.assertEqual(issue.milestone, None)
 
     def test_api_change_milestone_issue_remove_milestone2(self):
-        """ Test the api_change_milestone_issue method of the flask api. """
+        """Test the api_change_milestone_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -3151,7 +3416,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         self.assertEqual(issue.milestone, None)
 
     def test_api_change_milestone_issue_unauthorized(self):
-        """ Test the api_change_milestone_issue method of the flask api. """
+        """Test the api_change_milestone_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -3201,7 +3466,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         MagicMock(side_effect=pagure.exceptions.PagureException("error")),
     )
     def test_api_change_milestone_issue_raises_exception(self):
-        """ Test the api_change_milestone_issue method of the flask api. """
+        """Test the api_change_milestone_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -3294,7 +3559,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_api_view_issue_comment(self, p_send_email, p_ugt):
-        """ Test the api_view_issue_comment endpoint. """
+        """Test the api_view_issue_comment endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3372,6 +3637,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
                 "reactions": {},
                 "user": {
                     "fullname": "PY C",
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "name": "pingou",
                     "url_path": "user/pingou",
                 },
@@ -3400,6 +3666,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
                 "reactions": {},
                 "user": {
                     "fullname": "PY C",
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "name": "pingou",
                     "url_path": "user/pingou",
                 },
@@ -3409,7 +3676,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_api_view_issue_comment_private(self, p_send_email, p_ugt):
-        """ Test the api_view_issue_comment endpoint. """
+        """Test the api_view_issue_comment endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3499,6 +3766,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
                 "reactions": {},
                 "user": {
                     "fullname": "foo bar",
+                    "full_url": "http://localhost.localdomain/user/foo",
                     "name": "foo",
                     "url_path": "user/foo",
                 },
@@ -3509,7 +3777,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         "pagure.config.config", {"ENABLE_TICKETS_NAMESPACE": ["foobar"]}
     )
     def test_api_assign_issue_wrong_namespace(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -3554,10 +3822,13 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
             pagure.api.APIERROR.ETRACKERDISABLED.name, data["error_code"]
         )
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_api_assign_issue(self, p_send_email, p_ugt):
-        """ Test the api_assign_issue method of the flask api. """
+        """Test the api_assign_issue method of the flask api."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3620,17 +3891,194 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         data = {"assignee": "pingou"}
 
         # Valid request
-        output = self.app.post(
-            "/api/0/test/issue/1/assign", data=data, headers=headers
-        )
+        with testing.mock_sends(
+            pagure_messages.IssueAssignedAddedV1(
+                topic="pagure.issue.assigned.added",
+                body={
+                    "issue": {
+                        "id": 1,
+                        "title": "Test issue #1",
+                        "content": "We should work on this",
+                        "status": "Open",
+                        "close_status": None,
+                        "date_created": ANY,
+                        "last_updated": ANY,
+                        "closed_at": None,
+                        "user": {
+                            "name": "pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                        },
+                        "private": False,
+                        "tags": [],
+                        "depends": [],
+                        "blocks": [],
+                        "assignee": {
+                            "name": "pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                        },
+                        "priority": None,
+                        "milestone": None,
+                        "custom_fields": [],
+                        "full_url": "http://localhost.localdomain/test/issue/1",
+                        "closed_by": None,
+                        "related_prs": [],
+                        "comments": [],
+                    },
+                    "project": {
+                        "id": 1,
+                        "name": "test",
+                        "fullname": "test",
+                        "url_path": "test",
+                        "description": "test project #1",
+                        "full_url": "http://localhost.localdomain/test",
+                        "namespace": None,
+                        "parent": None,
+                        "date_created": ANY,
+                        "date_modified": ANY,
+                        "user": {
+                            "name": "pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                        },
+                        "access_users": {
+                            "owner": ["pingou"],
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "access_groups": {
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "tags": [],
+                        "priorities": {},
+                        "custom_keys": [],
+                        "close_status": [
+                            "Invalid",
+                            "Insufficient data",
+                            "Fixed",
+                            "Duplicate",
+                        ],
+                        "milestones": {},
+                    },
+                    "agent": "pingou",
+                },
+            )
+        ):
+            output = self.app.post(
+                "/api/0/test/issue/1/assign", data=data, headers=headers
+            )
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
         self.assertDictEqual(data, {"message": "Issue assigned to pingou"})
 
         # Un-assign
-        output = self.app.post(
-            "/api/0/test/issue/1/assign", data=data, headers=headers
-        )
+        with testing.mock_sends(
+            pagure_messages.IssueAssignedResetV1(
+                topic="pagure.issue.assigned.reset",
+                body={
+                    "issue": {
+                        "id": 1,
+                        "title": "Test issue #1",
+                        "content": "We should work on this",
+                        "status": "Open",
+                        "close_status": None,
+                        "date_created": ANY,
+                        "last_updated": ANY,
+                        "closed_at": None,
+                        "user": {
+                            "name": "pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                        },
+                        "private": False,
+                        "tags": [],
+                        "depends": [],
+                        "blocks": [],
+                        "assignee": None,
+                        "priority": None,
+                        "milestone": None,
+                        "custom_fields": [],
+                        "full_url": "http://localhost.localdomain/test/issue/1",
+                        "closed_by": None,
+                        "related_prs": [],
+                        "comments": [
+                            {
+                                "id": 1,
+                                "comment": "**Metadata Update from @pingou**:"
+                                "\n- Issue assigned to pingou",
+                                "parent": None,
+                                "date_created": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                },
+                                "edited_on": None,
+                                "editor": None,
+                                "notification": True,
+                                "reactions": {},
+                            }
+                        ],
+                    },
+                    "project": {
+                        "id": 1,
+                        "name": "test",
+                        "fullname": "test",
+                        "url_path": "test",
+                        "description": "test project #1",
+                        "full_url": "http://localhost.localdomain/test",
+                        "namespace": None,
+                        "parent": None,
+                        "date_created": ANY,
+                        "date_modified": ANY,
+                        "user": {
+                            "name": "pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                        },
+                        "access_users": {
+                            "owner": ["pingou"],
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "access_groups": {
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "tags": [],
+                        "priorities": {},
+                        "custom_keys": [],
+                        "close_status": [
+                            "Invalid",
+                            "Insufficient data",
+                            "Fixed",
+                            "Duplicate",
+                        ],
+                        "milestones": {},
+                    },
+                    "agent": "pingou",
+                },
+            )
+        ):
+            output = self.app.post(
+                "/api/0/test/issue/1/assign", data=data, headers=headers
+            )
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
         self.assertDictEqual(data, {"message": "Assignee reset"})
@@ -3784,7 +4232,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_api_assign_issue_issuer(self, p_send_email, p_ugt):
-        """ Test the api_assign_issue method of the flask api. """
+        """Test the api_assign_issue method of the flask api."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3841,7 +4289,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_api_subscribe_issue(self, p_send_email, p_ugt):
-        """ Test the api_subscribe_issue method of the flask api. """
+        """Test the api_subscribe_issue method of the flask api."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -4022,7 +4470,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_update_custom_field(self):
-        """ Test the api_update_custom_field method of the flask api. """
+        """Test the api_update_custom_field method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -4252,7 +4700,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         MagicMock(side_effect=pagure.exceptions.PagureException("error")),
     )
     def test_api_update_custom_field_raises_error(self):
-        """ Test the api_update_custom_field method of the flask api. """
+        """Test the api_update_custom_field method of the flask api."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         tests.create_tokens(self.session)
@@ -4309,7 +4757,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         self.assertDictEqual(data, {"error": "error", "error_code": "ENOCODE"})
 
     def test_api_view_issues_history_stats(self):
-        """ Test the api_view_issues_history_stats method of the flask api. """
+        """Test the api_view_issues_history_stats method of the flask api."""
         self.test_api_new_issue()
 
         # Create private issue, closed and without a closed_at date
@@ -4338,7 +4786,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
             self.assertEqual(data["stats"][k], 0)
 
     def test_api_view_issues_history_stats_detailed(self):
-        """ Test the api_view_issues_history_stats method of the flask api. """
+        """Test the api_view_issues_history_stats method of the flask api."""
         self.test_api_new_issue()
 
         output = self.app.get("/api/0/test/issues/history/detailed_stats")
@@ -4358,9 +4806,54 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
                 {"closed_ticket": 0, "count": 0, "open_ticket": 0},
             )
 
+    def test_api_view_issues_history_stats_detailed_invalid_range(self):
+        """Test the api_view_issues_history_stats method of the flask api."""
+        self.test_api_new_issue()
+
+        output = self.app.get(
+            "/api/0/test/issues/history/detailed_stats?weeks_range=abc"
+        )
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+
+        self.assertEqual(list(data.keys()), ["stats"])
+        self.assertEqual(len(data["stats"]), 53)
+        last_key = sorted(data["stats"].keys())[-1]
+        self.assertEqual(
+            data["stats"][last_key],
+            {"closed_ticket": 0, "count": 0, "open_ticket": 1},
+        )
+        for k in sorted(data["stats"].keys())[:-1]:
+            self.assertEqual(
+                data["stats"][k],
+                {"closed_ticket": 0, "count": 0, "open_ticket": 0},
+            )
+
+    def test_api_view_issues_history_stats_detailed_one_week(self):
+        """Test the api_view_issues_history_stats method of the flask api."""
+        self.test_api_new_issue()
+
+        output = self.app.get(
+            "/api/0/test/issues/history/detailed_stats?weeks_range=1"
+        )
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+
+        self.assertEqual(list(data.keys()), ["stats"])
+        self.assertEqual(len(data["stats"]), 1)
+        last_key = sorted(data["stats"].keys())[-1]
+        self.assertEqual(
+            data["stats"][last_key],
+            {"closed_ticket": 0, "count": 0, "open_ticket": 1},
+        )
+        for k in sorted(data["stats"].keys())[:-1]:
+            self.assertEqual(
+                data["stats"][k],
+                {"closed_ticket": 0, "count": 0, "open_ticket": 0},
+            )
+
     def test_api_view_user_issues_pingou(self):
-        """ Test the api_view_user_issues method of the flask api for pingou.
-        """
+        """Test the api_view_user_issues method of the flask api for pingou."""
         self.test_api_new_issue()
 
         # Create private issue
@@ -4489,8 +4982,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         self.assertEqual(data["total_issues_created_pages"], 1)
 
     def test_api_view_user_issues_foo(self):
-        """ Test the api_view_user_issues method of the flask api for foo.
-        """
+        """Test the api_view_user_issues method of the flask api for foo."""
         self.test_api_new_issue()
 
         # Create private issue
@@ -4535,8 +5027,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         self.assertEqual(data["total_issues_created_pages"], 1)
 
     def test_api_view_user_issues_foo_invalid_page(self):
-        """ Test the api_view_user_issues method of the flask api for foo.
-        """
+        """Test the api_view_user_issues method of the flask api for foo."""
         self.test_api_new_issue()
 
         output = self.app.get("/api/0/user/foo/issues?page=0")
@@ -4564,8 +5055,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         )
 
     def test_api_view_user_issues_foo_no_assignee(self):
-        """ Test the api_view_user_issues method of the flask api for foo.
-        """
+        """Test the api_view_user_issues method of the flask api for foo."""
         self.test_api_new_issue()
 
         output = self.app.get("/api/0/user/foo/issues?assignee=0")
@@ -4596,8 +5086,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         self.assertEqual(data["total_issues_created_pages"], 1)
 
     def test_api_view_user_issues_pingou_no_author(self):
-        """ Test the api_view_user_issues method of the flask api for pingou.
-        """
+        """Test the api_view_user_issues method of the flask api for pingou."""
         self.test_api_new_issue()
 
         output = self.app.get("/api/0/user/pingou/issues?author=0")
@@ -4628,7 +5117,7 @@ class PagureFlaskApiIssuetests(tests.SimplePagureTest):
         self.assertEqual(data["total_issues_created_pages"], 1)
 
     def api_api_view_issue_user_token(self):
-        """ Testhe the api view issues of the flask api with valid user token """
+        """Testhe the api view issues of the flask api with valid user token"""
         tests.create_projects(self.session)
         tests.create_projects_git(
             os.path.join(self.path, "tickets", bare=True)
diff --git a/tests/test_pagure_flask_api_issue_change_status.py b/tests/test_pagure_flask_api_issue_change_status.py
index a15b337..0723fdd 100644
--- a/tests/test_pagure_flask_api_issue_change_status.py
+++ b/tests/test_pagure_flask_api_issue_change_status.py
@@ -31,13 +31,13 @@ import tests
 
 
 class PagureFlaskApiIssueChangeStatustests(tests.Modeltests):
-    """ Tests for the flask API of pagure for changing the status of an
+    """Tests for the flask API of pagure for changing the status of an
     issue
     """
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiIssueChangeStatustests, self).setUp()
 
         pagure.config.config["TICKETS_FOLDER"] = None
@@ -97,7 +97,7 @@ class PagureFlaskApiIssueChangeStatustests(tests.Modeltests):
         tests.create_tokens_acl(self.session, token_id="project-less-pingou")
 
     def test_api_change_status_issue_invalid_project(self):
-        """ Test the api_change_status_issue method of the flask api. """
+        """Test the api_change_status_issue method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -110,7 +110,7 @@ class PagureFlaskApiIssueChangeStatustests(tests.Modeltests):
         )
 
     def test_api_change_status_issue_token_not_for_project(self):
-        """ Test the api_change_status_issue method of the flask api. """
+        """Test the api_change_status_issue method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -124,7 +124,7 @@ class PagureFlaskApiIssueChangeStatustests(tests.Modeltests):
         self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])
 
     def test_api_change_status_issue_invalid_issue(self):
-        """ Test the api_change_status_issue method of the flask api. """
+        """Test the api_change_status_issue method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -137,7 +137,7 @@ class PagureFlaskApiIssueChangeStatustests(tests.Modeltests):
         )
 
     def test_api_change_status_issue_incomplete(self):
-        """ Test the api_change_status_issue method of the flask api. """
+        """Test the api_change_status_issue method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -169,7 +169,7 @@ class PagureFlaskApiIssueChangeStatustests(tests.Modeltests):
         self.assertEqual(issue.status, "Open")
 
     def test_api_change_status_issue_no_change(self):
-        """ Test the api_change_status_issue method of the flask api. """
+        """Test the api_change_status_issue method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -194,7 +194,7 @@ class PagureFlaskApiIssueChangeStatustests(tests.Modeltests):
         MagicMock(side_effect=pagure.exceptions.PagureException("error")),
     )
     def test_api_change_status_issue_raise_error(self):
-        """ Test the api_change_status_issue method of the flask api. """
+        """Test the api_change_status_issue method of the flask api."""
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         close_status = repo.close_status
         close_status = ["Fixed", "Upstream", "Invalid"]
@@ -217,7 +217,7 @@ class PagureFlaskApiIssueChangeStatustests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_change_status_issue(self):
-        """ Test the api_change_status_issue method of the flask api. """
+        """Test the api_change_status_issue method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -255,7 +255,7 @@ class PagureFlaskApiIssueChangeStatustests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_change_status_issue_closed_status(self):
-        """ Test the api_change_status_issue method of the flask api. """
+        """Test the api_change_status_issue method of the flask api."""
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         close_status = repo.close_status
         close_status = ["Fixed", "Upstream", "Invalid"]
@@ -286,7 +286,7 @@ class PagureFlaskApiIssueChangeStatustests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_change_status_issue_no_ticket_project_less(self):
-        """ Test the api_change_status_issue method of the flask api. """
+        """Test the api_change_status_issue method of the flask api."""
 
         headers = {"Authorization": "token project-less-foo"}
 
@@ -309,7 +309,7 @@ class PagureFlaskApiIssueChangeStatustests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_change_status_issue_project_less(self):
-        """ Test the api_change_status_issue method of the flask api. """
+        """Test the api_change_status_issue method of the flask api."""
 
         headers = {"Authorization": "token project-less-pingou"}
 
diff --git a/tests/test_pagure_flask_api_issue_comment.py b/tests/test_pagure_flask_api_issue_comment.py
index 6c8f007..a0f5d3f 100644
--- a/tests/test_pagure_flask_api_issue_comment.py
+++ b/tests/test_pagure_flask_api_issue_comment.py
@@ -28,13 +28,13 @@ import tests  # noqa: E402
 
 
 class PagureFlaskApiIssueCommenttests(tests.Modeltests):
-    """ Tests for the flask API of pagure for changing the status of an
+    """Tests for the flask API of pagure for changing the status of an
     issue
     """
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiIssueCommenttests, self).setUp()
 
         pagure.config.config["TICKETS_FOLDER"] = None
@@ -82,7 +82,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
         tests.create_tokens_acl(self.session, token_id="project-less-foo")
 
     def test_api_comment_issue_invalid_project(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -95,7 +95,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
         )
 
     def test_api_comment_issue_invalid_project_token(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -109,7 +109,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
         self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])
 
     def test_api_comment_issue_invalid_issue(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         # Invalid issue
@@ -121,7 +121,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
         )
 
     def test_api_comment_issue_incomplete_request(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         # Check comments before
@@ -152,7 +152,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
         self.assertEqual(issue.status, "Open")
 
     def test_api_comment_issue(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -180,7 +180,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
         self.assertEqual(len(issue.comments), 1)
 
     def test_api_comment_issue_private_un_authorized(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         # Check before
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -208,7 +208,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_comment_issue_private(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         # Create token for user foo
         item = pagure.lib.model.Token(
@@ -242,7 +242,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
         )
 
     def test_api_comment_issue_invalid_project_project_less(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         headers = {"Authorization": "token project-less-foo"}
 
@@ -255,7 +255,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
         )
 
     def test_api_comment_issue_invalid_project_token_project_less(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         headers = {"Authorization": "token project-less-foo"}
 
@@ -268,7 +268,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
         )
 
     def test_api_comment_issue_invalid_issue_project_less(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         headers = {"Authorization": "token project-less-foo"}
         # Invalid issue
@@ -280,7 +280,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
         )
 
     def test_api_comment_issue_incomplete_request_project_less(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         headers = {"Authorization": "token project-less-foo"}
         # Check comments before
@@ -312,7 +312,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_comment_issue_project_less(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         headers = {"Authorization": "token project-less-foo"}
 
@@ -340,7 +340,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
         self.assertEqual(len(issue.comments), 1)
 
     def test_api_comment_issue_private_un_authorized_project_less(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         # Check before
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -368,7 +368,7 @@ class PagureFlaskApiIssueCommenttests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_comment_issue_private_project_less(self):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
 
         # Create token for user foo
         item = pagure.lib.model.Token(
diff --git a/tests/test_pagure_flask_api_issue_create.py b/tests/test_pagure_flask_api_issue_create.py
index e359c3f..6054ff1 100644
--- a/tests/test_pagure_flask_api_issue_create.py
+++ b/tests/test_pagure_flask_api_issue_create.py
@@ -27,12 +27,11 @@ import tests  # noqa: E402
 
 
 class PagureFlaskApiIssueCreatetests(tests.Modeltests):
-    """ Tests for the flask API of pagure for creating an issue
-    """
+    """Tests for the flask API of pagure for creating an issue"""
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiIssueCreatetests, self).setUp()
 
         pagure.config.config["TICKETS_FOLDER"] = None
@@ -67,7 +66,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
         tests.create_tokens_acl(self.session, token_id="project-specific-foo")
 
     def test_create_issue_own_project_no_data(self):
-        """ Test creating a new ticket on a project for which you're the
+        """Test creating a new ticket on a project for which you're the
         main maintainer.
         """
 
@@ -91,7 +90,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
         )
 
     def test_create_issue_own_project_incomplete_data(self):
-        """ Test creating a new ticket on a project for which you're the
+        """Test creating a new ticket on a project for which you're the
         main maintainer.
         """
 
@@ -116,7 +115,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
         )
 
     def test_create_issue_own_project(self):
-        """ Test creating a new ticket on a project for which you're the
+        """Test creating a new ticket on a project for which you're the
         main maintainer.
         """
 
@@ -149,6 +148,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
                     "comments": [],
                     "content": "This issue needs attention",
                     "custom_fields": [],
+                    "full_url": "http://localhost.localdomain/test/issue/1",
                     "date_created": "1431414800",
                     "depends": [],
                     "id": 1,
@@ -162,6 +162,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
                     "title": "test issue",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -172,7 +173,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_create_issue_someone_else_project_project_less_token(self):
-        """ Test creating a new ticket on a project with which you have
+        """Test creating a new ticket on a project with which you have
         nothing to do.
         """
 
@@ -205,6 +206,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
                     "comments": [],
                     "content": "This issue needs attention",
                     "custom_fields": [],
+                    "full_url": "http://localhost.localdomain/test/issue/1",
                     "date_created": "1431414800",
                     "depends": [],
                     "id": 1,
@@ -218,6 +220,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
                     "title": "test issue",
                     "user": {
                         "fullname": "foo bar",
+                        "full_url": "http://localhost.localdomain/user/foo",
                         "name": "foo",
                         "url_path": "user/foo",
                     },
@@ -228,7 +231,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_create_issue_project_specific_token(self):
-        """ Test creating a new ticket on a project with a regular
+        """Test creating a new ticket on a project with a regular
         project-specific token.
         """
 
@@ -261,6 +264,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
                     "comments": [],
                     "content": "This issue needs attention",
                     "custom_fields": [],
+                    "full_url": "http://localhost.localdomain/test/issue/1",
                     "date_created": "1431414800",
                     "depends": [],
                     "id": 1,
@@ -274,6 +278,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
                     "title": "test issue",
                     "user": {
                         "fullname": "foo bar",
+                        "full_url": "http://localhost.localdomain/user/foo",
                         "name": "foo",
                         "url_path": "user/foo",
                     },
@@ -284,7 +289,7 @@ class PagureFlaskApiIssueCreatetests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_create_issue_invalid_project_specific_token(self):
-        """ Test creating a new ticket on a project with a regular
+        """Test creating a new ticket on a project with a regular
         project-specific token but for another project.
         """
 
diff --git a/tests/test_pagure_flask_api_issue_custom_fields.py b/tests/test_pagure_flask_api_issue_custom_fields.py
index a86892b..cfd2d2e 100644
--- a/tests/test_pagure_flask_api_issue_custom_fields.py
+++ b/tests/test_pagure_flask_api_issue_custom_fields.py
@@ -24,10 +24,10 @@ import tests  # noqa: E402
 
 
 class PagureFlaskApiCustomFieldIssuetests(tests.Modeltests):
-    """ Tests for the flask API of pagure for issue's custom fields """
+    """Tests for the flask API of pagure for issue's custom fields"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         self.maxDiff = None
         super(PagureFlaskApiCustomFieldIssuetests, self).setUp()
 
@@ -51,7 +51,7 @@ class PagureFlaskApiCustomFieldIssuetests(tests.Modeltests):
         self.session.commit()
 
     def test_api_update_custom_field_bad_request(self):
-        """ Test the api_update_custom_field method of the flask api.
+        """Test the api_update_custom_field method of the flask api.
         This test that a badly form request returns the correct error.
         """
 
@@ -73,7 +73,7 @@ class PagureFlaskApiCustomFieldIssuetests(tests.Modeltests):
         )
 
     def test_api_update_custom_field_wrong_field(self):
-        """ Test the api_update_custom_field method of the flask api.
+        """Test the api_update_custom_field method of the flask api.
         This test that an invalid field retruns the correct error.
         """
 
@@ -98,7 +98,7 @@ class PagureFlaskApiCustomFieldIssuetests(tests.Modeltests):
         MagicMock(side_effect=pagure.exceptions.PagureException("error")),
     )
     def test_api_update_custom_field_raise_error(self):
-        """ Test the api_update_custom_field method of the flask api.
+        """Test the api_update_custom_field method of the flask api.
         This test the successful requests scenarii.
         """
 
@@ -132,7 +132,7 @@ class PagureFlaskApiCustomFieldIssuetests(tests.Modeltests):
         self.assertDictEqual(data, {"error": "error", "error_code": "ENOCODE"})
 
     def test_api_update_custom_field(self):
-        """ Test the api_update_custom_field method of the flask api.
+        """Test the api_update_custom_field method of the flask api.
         This test the successful requests scenarii.
         """
 
diff --git a/tests/test_pagure_flask_api_issue_update.py b/tests/test_pagure_flask_api_issue_update.py
index 60d175f..99d76ee 100644
--- a/tests/test_pagure_flask_api_issue_update.py
+++ b/tests/test_pagure_flask_api_issue_update.py
@@ -24,10 +24,10 @@ import tests  # noqa: E402
 
 
 class PagureFlaskApiIssueUpdatetests(tests.Modeltests):
-    """ Tests for the flask API of pagure for updating an issue """
+    """Tests for the flask API of pagure for updating an issue"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiIssueUpdatetests, self).setUp()
 
         pagure.config.config["TICKETS_FOLDER"] = None
@@ -35,7 +35,7 @@ class PagureFlaskApiIssueUpdatetests(tests.Modeltests):
         tests.create_tokens(self.session)
 
     def test_api_issue_update_wrong_token(self):
-        """ Test the api_issue_update method of flask API """
+        """Test the api_issue_update method of flask API"""
         tests.create_tokens_acl(self.session)
         headers = {"Authorization": "token aaa"}
         output = self.app.post("/api/0/foo/issue/1", headers=headers)
@@ -51,7 +51,7 @@ class PagureFlaskApiIssueUpdatetests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_issue_update_wrong_project(self):
-        """ Test the api_issue_update method of flask API """
+        """Test the api_issue_update method of flask API"""
         tests.create_tokens_acl(self.session)
         headers = {"Authorization": "token aaabbbcccddd"}
         output = self.app.post("/api/0/foo/issue/1", headers=headers)
@@ -64,7 +64,7 @@ class PagureFlaskApiIssueUpdatetests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_issue_update_wrong_acls(self):
-        """ Test the api_issue_update method of flask API """
+        """Test the api_issue_update method of flask API"""
         tests.create_tokens_acl(self.session, acl_name="issue_create")
         headers = {"Authorization": "token aaabbbcccddd"}
         output = self.app.post("/api/0/test/issue/1", headers=headers)
@@ -81,7 +81,7 @@ class PagureFlaskApiIssueUpdatetests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"ENABLE_TICKETS": False})
     def test_api_issue_update_instance_tickets_disabled(self):
-        """ Test the api_issue_update method of flask API """
+        """Test the api_issue_update method of flask API"""
         tests.create_tokens_acl(self.session)
         headers = {"Authorization": "token aaabbbcccddd"}
         output = self.app.post("/api/0/test/issue/1", headers=headers)
@@ -94,7 +94,7 @@ class PagureFlaskApiIssueUpdatetests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_issue_update_project_tickets_disabled(self):
-        """ Test the api_issue_update method of flask API """
+        """Test the api_issue_update method of flask API"""
         tests.create_tokens_acl(self.session)
         # disable tickets on this repo
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -114,7 +114,7 @@ class PagureFlaskApiIssueUpdatetests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_issue_update_project_read_only_issue_tracker(self):
-        """ Test the api_issue_update method of flask API """
+        """Test the api_issue_update method of flask API"""
         tests.create_tokens_acl(self.session)
         # set read only issue tracke on this repo
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -134,7 +134,7 @@ class PagureFlaskApiIssueUpdatetests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_issue_update_wrong_issue(self):
-        """ Test the api_issue_update method of flask API """
+        """Test the api_issue_update method of flask API"""
         tests.create_tokens_acl(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         headers = {"Authorization": "token aaabbbcccddd"}
@@ -145,7 +145,7 @@ class PagureFlaskApiIssueUpdatetests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_issue_update_no_input(self):
-        """ Test the api_issue_update method of flask API """
+        """Test the api_issue_update method of flask API"""
         tests.create_tokens_acl(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         headers = {"Authorization": "token aaabbbcccddd"}
@@ -177,7 +177,7 @@ class PagureFlaskApiIssueUpdatetests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_issue_update_partial_input(self):
-        """ Test the api_issue_update method of flask API """
+        """Test the api_issue_update method of flask API"""
         tests.create_tokens_acl(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         headers = {"Authorization": "token aaabbbcccddd"}
@@ -223,7 +223,7 @@ class PagureFlaskApiIssueUpdatetests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_issue_update(self):
-        """ Test the api_issue_update method of flask API """
+        """Test the api_issue_update method of flask API"""
         tests.create_tokens_acl(self.session)
         tests.create_projects_git(os.path.join(self.path, "tickets"))
         headers = {"Authorization": "token aaabbbcccddd"}
diff --git a/tests/test_pagure_flask_api_plugins_install.py b/tests/test_pagure_flask_api_plugins_install.py
index 96a12fe..836bf92 100644
--- a/tests/test_pagure_flask_api_plugins_install.py
+++ b/tests/test_pagure_flask_api_plugins_install.py
@@ -27,12 +27,11 @@ import tests  # noqa: E402
 
 
 class PagureFlaskApiPluginInstalltests(tests.Modeltests):
-    """ Tests for the flask API of pagure for installing a plugin
-    """
+    """Tests for the flask API of pagure for installing a plugin"""
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiPluginInstalltests, self).setUp()
 
         tests.create_projects(self.session)
@@ -64,7 +63,7 @@ class PagureFlaskApiPluginInstalltests(tests.Modeltests):
         tests.create_tokens_acl(self.session, token_id="project-specific-foo")
 
     def test_install_plugin_own_project_no_data(self):
-        """ Test installing a new plugin on a project for which you're the
+        """Test installing a new plugin on a project for which you're the
         main maintainer.
         """
 
@@ -86,7 +85,7 @@ class PagureFlaskApiPluginInstalltests(tests.Modeltests):
         )
 
     def test_install_plugin_own_project(self):
-        """ Test installing a new plugin on a project for which you're the
+        """Test installing a new plugin on a project for which you're the
         main maintainer.
         """
 
@@ -112,7 +111,7 @@ class PagureFlaskApiPluginInstalltests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_install_plugin_someone_else_project_project_less_token(self):
-        """ Test installing a new plugin on a project with which you have
+        """Test installing a new plugin on a project with which you have
         nothing to do.
         """
 
@@ -138,7 +137,7 @@ class PagureFlaskApiPluginInstalltests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_install_plugin_project_specific_token(self):
-        """ Test installing a new plugin on a project with a regular
+        """Test installing a new plugin on a project with a regular
         project-specific token.
         """
 
@@ -164,7 +163,7 @@ class PagureFlaskApiPluginInstalltests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_install_plugin_invalid_project_specific_token(self):
-        """ Test installing a new plugin on a project with a regular
+        """Test installing a new plugin on a project with a regular
         project-specific token but for another project.
         """
 
diff --git a/tests/test_pagure_flask_api_plugins_remove.py b/tests/test_pagure_flask_api_plugins_remove.py
index 4f3a141..9064c3a 100644
--- a/tests/test_pagure_flask_api_plugins_remove.py
+++ b/tests/test_pagure_flask_api_plugins_remove.py
@@ -28,12 +28,11 @@ import tests  # noqa: E402
 
 
 class PagureFlaskApiPluginRemovetests(tests.Modeltests):
-    """ Tests for the flask API of pagure for removing a plugin
-    """
+    """Tests for the flask API of pagure for removing a plugin"""
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiPluginRemovetests, self).setUp()
 
         tests.create_projects(self.session)
@@ -77,7 +76,7 @@ class PagureFlaskApiPluginRemovetests(tests.Modeltests):
         tests.create_tokens_acl(self.session, token_id="project-specific-foo")
 
     def test_remove_plugin_own_project_plugin_not_installed(self):
-        """ Test removing a plugin from a project for which you're the
+        """Test removing a plugin from a project for which you're the
         main maintainer and the plugin is not installed.
         """
 
@@ -98,7 +97,7 @@ class PagureFlaskApiPluginRemovetests(tests.Modeltests):
         )
 
     def test_remove_plugin_own_project(self):
-        """ Test removing a plugin from a project for which you're the
+        """Test removing a plugin from a project for which you're the
         main maintainer.
         """
 
@@ -121,7 +120,7 @@ class PagureFlaskApiPluginRemovetests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_remove_plugin_someone_else_project_project_less_token(self):
-        """ Test removing a plugin from a project with which you have
+        """Test removing a plugin from a project with which you have
         nothing to do.
         """
 
@@ -144,7 +143,7 @@ class PagureFlaskApiPluginRemovetests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_remove_plugin_project_specific_token(self):
-        """ Test removing a plugin from a project with a regular
+        """Test removing a plugin from a project with a regular
         project-specific token.
         """
 
@@ -167,7 +166,7 @@ class PagureFlaskApiPluginRemovetests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_remove_plugin_invalid_project_specific_token(self):
-        """ Test removing a plugin from a project with a regular
+        """Test removing a plugin from a project with a regular
         project-specific token but for another project.
         """
 
diff --git a/tests/test_pagure_flask_api_plugins_view.py b/tests/test_pagure_flask_api_plugins_view.py
index 8cdf91a..00466e1 100644
--- a/tests/test_pagure_flask_api_plugins_view.py
+++ b/tests/test_pagure_flask_api_plugins_view.py
@@ -25,12 +25,10 @@ import tests  # noqa: E402
 
 
 class PagureFlaskApiPluginViewtests(tests.Modeltests):
-    """ Tests for the flask API of pagure for viewing plugins
-    """
+    """Tests for the flask API of pagure for viewing plugins"""
 
     def test_view_plugin(self):
-        """ Test viewing every plugin available in pagure.
-        """
+        """Test viewing every plugin available in pagure."""
 
         output = self.app.get("/api/0/_plugins")
         self.assertEqual(output.status_code, 200)
@@ -76,8 +74,7 @@ class PagureFlaskApiPluginViewtests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"DISABLED_PLUGINS": ["IRC"]})
     def test_view_plugin_disabled(self):
-        """ Test viewing every plugin available in pagure with one plugin disabled.
-        """
+        """Test viewing every plugin available in pagure with one plugin disabled."""
 
         output = self.app.get("/api/0/_plugins")
         self.assertEqual(output.status_code, 200)
diff --git a/tests/test_pagure_flask_api_plugins_view_project.py b/tests/test_pagure_flask_api_plugins_view_project.py
index 09d887e..77a9b10 100644
--- a/tests/test_pagure_flask_api_plugins_view_project.py
+++ b/tests/test_pagure_flask_api_plugins_view_project.py
@@ -27,19 +27,17 @@ import tests  # noqa: E402
 
 
 class PagureFlaskApiPluginViewProjecttests(tests.Modeltests):
-    """ Tests for the flask API of pagure for viewing enabled plugins on project
-    """
+    """Tests for the flask API of pagure for viewing enabled plugins on project"""
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiPluginViewProjecttests, self).setUp()
 
         tests.create_projects(self.session)
 
     def test_view_plugin_on_project(self):
-        """ Test viewing plugins on a project.
-        """
+        """Test viewing plugins on a project."""
 
         # Install plugin
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -66,7 +64,7 @@ class PagureFlaskApiPluginViewProjecttests(tests.Modeltests):
         )
 
     def test_viewing_plugin_on_project_no_plugin(self):
-        """ Test viewing plugins on a project, which doesn't
+        """Test viewing plugins on a project, which doesn't
         have any installed.
         """
 
diff --git a/tests/test_pagure_flask_api_pr_flag.py b/tests/test_pagure_flask_api_pr_flag.py
index 70443ab..2d73e16 100644
--- a/tests/test_pagure_flask_api_pr_flag.py
+++ b/tests/test_pagure_flask_api_pr_flag.py
@@ -15,7 +15,9 @@ import sys
 import os
 
 import json
-from mock import patch, MagicMock
+import pagure_messages
+from fedora_messaging import api, testing
+from mock import ANY, patch, MagicMock
 
 sys.path.insert(
     0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
@@ -27,13 +29,13 @@ import tests  # noqa
 
 
 class PagureFlaskApiPRFlagtests(tests.Modeltests):
-    """ Tests for the flask API of pagure for flagging pull-requests """
+    """Tests for the flask API of pagure for flagging pull-requests"""
 
     maxDiff = None
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiPRFlagtests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -65,10 +67,13 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         request = pagure.lib.query.search_pull_requests(
             self.session, project_id=1, requestid=1
         )
+        request.commit_stop = "hash_commit_stop"
+        self.session.add(request)
+        self.session.commit()
         self.assertEqual(len(request.flags), 0)
 
     def test_invalid_project(self):
-        """ Test the flagging a PR on an invalid project. """
+        """Test the flagging a PR on an invalid project."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -83,7 +88,7 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         )
 
     def test_incorrect_project(self):
-        """ Test the flagging a PR on the wrong project. """
+        """Test the flagging a PR on the wrong project."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         # Valid token, wrong project
@@ -98,7 +103,7 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])
 
     def test_pr_disabled(self):
-        """ Test the flagging a PR when PRs are disabled. """
+        """Test the flagging a PR when PRs are disabled."""
 
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         settings = repo.settings
@@ -124,7 +129,7 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         )
 
     def test_no_pr(self):
-        """ Test the flagging a PR when the PR doesn't exist. """
+        """Test the flagging a PR when the PR doesn't exist."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         # No PR
@@ -138,7 +143,7 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         )
 
     def test_no_input(self):
-        """ Test the flagging an existing PR but with no data. """
+        """Test the flagging an existing PR but with no data."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         # No input
@@ -161,7 +166,7 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         )
 
     def test_no_comment(self):
-        """ Test the flagging an existing PR but with incomplete data. """
+        """Test the flagging an existing PR but with incomplete data."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         data = {
@@ -198,7 +203,7 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         MagicMock(side_effect=pagure.exceptions.PagureException("error")),
     )
     def test_raise_exception(self):
-        """ Test the flagging a PR when adding a flag raises an exception. """
+        """Test the flagging a PR when adding a flag raises an exception."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {
@@ -216,9 +221,12 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         data = json.loads(output.get_data(as_text=True))
         self.assertDictEqual(data, {"error": "error", "error_code": "ENOCODE"})
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.notify.send_email")
     def test_flagging_a_pul_request_with_notification(self, mock_email):
-        """ Test the flagging a PR. """
+        """Test the flagging a PR."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         # Enable PR notifications
@@ -237,15 +245,151 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         }
 
         # Valid request
-        output = self.app.post(
-            "/api/0/test/pull-request/1/flag", data=data, headers=headers
-        )
-        self.assertEqual(output.status_code, 200)
+        with testing.mock_sends(
+            pagure_messages.CommitFlagAddedV1,
+            pagure_messages.PullRequestFlagAddedV1(
+                topic="pagure.pull-request.flag.added",
+                body={
+                    "pullrequest": {
+                        "id": 1,
+                        "uid": ANY,
+                        "title": "test pull-request",
+                        "branch": "master",
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "description": "test project #1",
+                            "full_url": "http://localhost.localdomain/test",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                                "url_path": "user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "branch_from": "master",
+                        "repo_from": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "description": "test project #1",
+                            "full_url": "http://localhost.localdomain/test",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                                "url_path": "user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "remote_git": None,
+                        "date_created": ANY,
+                        "full_url": "http://localhost.localdomain/test/pull-request/1",
+                        "updated_on": ANY,
+                        "last_updated": ANY,
+                        "closed_at": None,
+                        "user": {
+                            "name": "pingou",
+                            "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "url_path": "user/pingou",
+                        },
+                        "assignee": None,
+                        "status": "Open",
+                        "commit_start": None,
+                        "commit_stop": "hash_commit_stop",
+                        "closed_by": None,
+                        "initial_comment": None,
+                        "cached_merge_status": "unknown",
+                        "threshold_reached": None,
+                        "tags": [],
+                        "comments": [],
+                    },
+                    "flag": {
+                        "commit_hash": "hash_commit_stop",
+                        "username": "Jenkins",
+                        "percent": None,
+                        "comment": "Tests running",
+                        "status": "pending",
+                        "url": "http://jenkins.cloud.fedoraproject.org/",
+                        "date_created": ANY,
+                        "date_updated": ANY,
+                        "user": {
+                            "name": "pingou",
+                            "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "url_path": "user/pingou",
+                        },
+                    },
+                    "agent": "pingou",
+                },
+            ),
+        ):
+            output = self.app.post(
+                "/api/0/test/pull-request/1/flag", data=data, headers=headers
+            )
+            self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
         data["flag"]["date_created"] = "1510742565"
         data["flag"]["date_updated"] = "1510742565"
-        pr_uid = data["flag"]["pull_request_uid"]
-        data["flag"]["pull_request_uid"] = "62b49f00d489452994de5010565fab81"
+        commit_hash = data["flag"]["commit_hash"]
+        data["flag"]["commit_hash"] = "62b49f00d489452994de5010565fab81"
         data["avatar_url"] = "https://seccdn.libravatar.org/avatar/..."
         self.assertDictEqual(
             data,
@@ -255,12 +399,13 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
                     "date_created": "1510742565",
                     "date_updated": "1510742565",
                     "percent": None,
-                    "pull_request_uid": "62b49f00d489452994de5010565fab81",
+                    "commit_hash": "62b49f00d489452994de5010565fab81",
                     "status": "pending",
                     "url": "http://jenkins.cloud.fedoraproject.org/",
                     "user": {
                         "default_email": "bar@pingou.com",
                         "emails": ["bar@pingou.com", "foo@pingou.com"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -279,9 +424,13 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         request = pagure.lib.query.search_pull_requests(
             self.session, project_id=1, requestid=1
         )
-        self.assertEqual(len(request.flags), 1)
-        self.assertEqual(request.flags[0].comment, "Tests running")
-        self.assertEqual(request.flags[0].percent, None)
+        self.assertEqual(len(request.flags), 0)
+
+        flags = pagure.lib.query.get_commit_flag(
+            self.session, request.project, commit_hash
+        )
+        self.assertEqual(flags[0].comment, "Tests running")
+        self.assertEqual(flags[0].percent, None)
 
         # Check the notification sent
         mock_email.assert_called_once_with(
@@ -291,15 +440,18 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
             "PR #1 - Jenkins: pending",
             "bar@pingou.com",
             assignee=None,
-            in_reply_to="test-pull-request-" + pr_uid,
-            mail_id="test-pull-request-" + pr_uid + "-1",
+            in_reply_to="test-pull-request-%s" % request.uid,
+            mail_id="test-commit-1-1",
             project_name="test",
             reporter="pingou",
             user_from="Jenkins",
         )
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     def test_updating_flag(self):
-        """ Test the updating the flag of a PR. """
+        """Test the updating the flag of a PR."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         data = {
@@ -317,22 +469,22 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         data = json.loads(output.get_data(as_text=True))
         data["flag"]["date_created"] = "1510742565"
         data["flag"]["date_updated"] = "1510742565"
-        data["flag"]["pull_request_uid"] = "62b49f00d489452994de5010565fab81"
         data["avatar_url"] = "https://seccdn.libravatar.org/avatar/..."
         self.assertDictEqual(
             data,
             {
                 "flag": {
                     "comment": "Tests running",
+                    "commit_hash": "hash_commit_stop",
                     "date_created": "1510742565",
                     "date_updated": "1510742565",
                     "percent": None,
-                    "pull_request_uid": "62b49f00d489452994de5010565fab81",
                     "status": "pending",
                     "url": "http://jenkins.cloud.fedoraproject.org/",
                     "user": {
                         "default_email": "bar@pingou.com",
                         "emails": ["bar@pingou.com", "foo@pingou.com"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -351,9 +503,13 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         request = pagure.lib.query.search_pull_requests(
             self.session, project_id=1, requestid=1
         )
-        self.assertEqual(len(request.flags), 1)
-        self.assertEqual(request.flags[0].comment, "Tests running")
-        self.assertEqual(request.flags[0].percent, None)
+        self.assertEqual(len(request.flags), 0)
+
+        flags = pagure.lib.query.get_commit_flag(
+            self.session, request.project, "hash_commit_stop"
+        )
+        self.assertEqual(flags[0].comment, "Tests running")
+        self.assertEqual(flags[0].percent, None)
 
         # Update flag  -  w/o providing the status
         data = {
@@ -364,29 +520,165 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
             "uid": "jenkins_build_pagure_100+seed",
         }
 
-        output = self.app.post(
-            "/api/0/test/pull-request/1/flag", data=data, headers=headers
-        )
+        with testing.mock_sends(
+            pagure_messages.CommitFlagUpdatedV1,
+            pagure_messages.PullRequestFlagUpdatedV1(
+                topic="pagure.pull-request.flag.updated",
+                body={
+                    "pullrequest": {
+                        "id": 1,
+                        "uid": ANY,
+                        "title": "test pull-request",
+                        "full_url": "http://localhost.localdomain/test/pull-request/1",
+                        "branch": "master",
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "description": "test project #1",
+                            "full_url": "http://localhost.localdomain/test",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                                "url_path": "user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "branch_from": "master",
+                        "repo_from": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "description": "test project #1",
+                            "full_url": "http://localhost.localdomain/test",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                                "url_path": "user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "remote_git": None,
+                        "date_created": ANY,
+                        "updated_on": ANY,
+                        "last_updated": ANY,
+                        "closed_at": None,
+                        "user": {
+                            "name": "pingou",
+                            "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "url_path": "user/pingou",
+                        },
+                        "assignee": None,
+                        "status": "Open",
+                        "commit_start": None,
+                        "commit_stop": "hash_commit_stop",
+                        "closed_by": None,
+                        "initial_comment": None,
+                        "cached_merge_status": "unknown",
+                        "threshold_reached": None,
+                        "tags": [],
+                        "comments": [],
+                    },
+                    "flag": {
+                        "commit_hash": "hash_commit_stop",
+                        "username": "Jenkins",
+                        "percent": 100,
+                        "comment": "Tests passed",
+                        "status": "success",
+                        "url": "http://jenkins.cloud.fedoraproject.org/",
+                        "date_created": ANY,
+                        "date_updated": ANY,
+                        "user": {
+                            "name": "pingou",
+                            "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "url_path": "user/pingou",
+                        },
+                    },
+                    "agent": "pingou",
+                },
+            ),
+        ):
+            output = self.app.post(
+                "/api/0/test/pull-request/1/flag", data=data, headers=headers
+            )
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
         data["flag"]["date_created"] = "1510742565"
         data["flag"]["date_updated"] = "1510742565"
-        data["flag"]["pull_request_uid"] = "62b49f00d489452994de5010565fab81"
         data["avatar_url"] = "https://seccdn.libravatar.org/avatar/..."
         self.assertDictEqual(
             data,
             {
                 "flag": {
                     "comment": "Tests passed",
+                    "commit_hash": "hash_commit_stop",
                     "date_created": "1510742565",
                     "date_updated": "1510742565",
                     "percent": 100,
-                    "pull_request_uid": "62b49f00d489452994de5010565fab81",
                     "status": "success",
                     "url": "http://jenkins.cloud.fedoraproject.org/",
                     "user": {
                         "default_email": "bar@pingou.com",
                         "emails": ["bar@pingou.com", "foo@pingou.com"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -405,12 +697,16 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         request = pagure.lib.query.search_pull_requests(
             self.session, project_id=1, requestid=1
         )
-        self.assertEqual(len(request.flags), 1)
-        self.assertEqual(request.flags[0].comment, "Tests passed")
-        self.assertEqual(request.flags[0].percent, 100)
+        self.assertEqual(len(request.flags), 0)
+
+        flags = pagure.lib.query.get_commit_flag(
+            self.session, request.project, "hash_commit_stop"
+        )
+        self.assertEqual(flags[0].comment, "Tests passed")
+        self.assertEqual(flags[0].percent, 100)
 
     def test_adding_two_flags(self):
-        """ Test the adding two flags to a PR. """
+        """Test the adding two flags to a PR."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         data = {
@@ -430,22 +726,22 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         data = json.loads(output.get_data(as_text=True))
         data["flag"]["date_created"] = "1510742565"
         data["flag"]["date_updated"] = "1510742565"
-        data["flag"]["pull_request_uid"] = "62b49f00d489452994de5010565fab81"
         data["avatar_url"] = "https://seccdn.libravatar.org/avatar/..."
         self.assertDictEqual(
             data,
             {
                 "flag": {
                     "comment": "Tests passed",
+                    "commit_hash": "hash_commit_stop",
                     "date_created": "1510742565",
                     "date_updated": "1510742565",
                     "percent": 100,
-                    "pull_request_uid": "62b49f00d489452994de5010565fab81",
                     "status": "success",
                     "url": "http://jenkins.cloud.fedoraproject.org/",
                     "user": {
                         "default_email": "bar@pingou.com",
                         "emails": ["bar@pingou.com", "foo@pingou.com"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -459,14 +755,19 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
             },
         )
 
-        # One flag added
+        # One flag added - but no longer on the request object
         self.session.commit()
         request = pagure.lib.query.search_pull_requests(
             self.session, project_id=1, requestid=1
         )
-        self.assertEqual(len(request.flags), 1)
-        self.assertEqual(request.flags[0].comment, "Tests passed")
-        self.assertEqual(request.flags[0].percent, 100)
+        self.assertEqual(len(request.flags), 0)
+
+        flags = pagure.lib.query.get_commit_flag(
+            self.session, request.project, "hash_commit_stop"
+        )
+        self.assertEqual(len(flags), 1)
+        self.assertEqual(flags[0].comment, "Tests passed")
+        self.assertEqual(flags[0].percent, 100)
 
         data = {
             "username": "Jenkins",
@@ -482,7 +783,6 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         data = json.loads(output.get_data(as_text=True))
         data["flag"]["date_created"] = "1510742565"
         data["flag"]["date_updated"] = "1510742565"
-        data["flag"]["pull_request_uid"] = "62b49f00d489452994de5010565fab81"
         self.assertNotEqual(data["uid"], "jenkins_build_pagure_100+seed")
         data["uid"] = "jenkins_build_pagure_100+seed"
         data["avatar_url"] = "https://seccdn.libravatar.org/avatar/..."
@@ -491,15 +791,16 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
             {
                 "flag": {
                     "comment": "Tests running again",
+                    "commit_hash": "hash_commit_stop",
                     "date_created": "1510742565",
                     "date_updated": "1510742565",
                     "percent": None,
-                    "pull_request_uid": "62b49f00d489452994de5010565fab81",
                     "status": "pending",
                     "url": "http://jenkins.cloud.fedoraproject.org/",
                     "user": {
                         "default_email": "bar@pingou.com",
                         "emails": ["bar@pingou.com", "foo@pingou.com"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -513,16 +814,21 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
             },
         )
 
-        # Two flag added
+        # Two flags added - but no longer on the request object
         self.session.commit()
         request = pagure.lib.query.search_pull_requests(
             self.session, project_id=1, requestid=1
         )
-        self.assertEqual(len(request.flags), 2)
-        self.assertEqual(request.flags[0].comment, "Tests running again")
-        self.assertEqual(request.flags[0].percent, None)
-        self.assertEqual(request.flags[1].comment, "Tests passed")
-        self.assertEqual(request.flags[1].percent, 100)
+        self.assertEqual(len(request.flags), 0)
+
+        flags = pagure.lib.query.get_commit_flag(
+            self.session, request.project, "hash_commit_stop"
+        )
+        self.assertEqual(len(flags), 2)
+        self.assertEqual(flags[1].comment, "Tests running again")
+        self.assertEqual(flags[1].percent, None)
+        self.assertEqual(flags[0].comment, "Tests passed")
+        self.assertEqual(flags[0].percent, 100)
 
     @patch.dict(
         "pagure.config.config",
@@ -539,7 +845,7 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
         },
     )
     def test_flagging_a_pull_request_while_having_custom_statuses(self):
-        """ Test flagging a PR while having custom statuses. """
+        """Test flagging a PR while having custom statuses."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         # No status and no percent => should use FLAG_PENDING
@@ -602,7 +908,7 @@ class PagureFlaskApiPRFlagtests(tests.Modeltests):
 
 
 class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
-    """ Tests for the flask API of pagure for flagging pull-requests using
+    """Tests for the flask API of pagure for flagging pull-requests using
     an user token (ie: not restricted to a specific project).
     """
 
@@ -610,7 +916,7 @@ class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiPRFlagUserTokentests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -642,10 +948,13 @@ class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
         request = pagure.lib.query.search_pull_requests(
             self.session, project_id=1, requestid=1
         )
+        request.commit_stop = "hash_commit_stop"
+        self.session.add(request)
+        self.session.commit()
         self.assertEqual(len(request.flags), 0)
 
     def test_no_pr(self):
-        """ Test flagging a non-existing PR. """
+        """Test flagging a non-existing PR."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         # Invalid project
@@ -659,7 +968,7 @@ class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
         )
 
     def test_no_pr_other_project(self):
-        """ Test flagging a non-existing PR on a different project. """
+        """Test flagging a non-existing PR on a different project."""
         headers = {"Authorization": "token aaabbbcccddd"}
         # Valid token, wrong project
         output = self.app.post(
@@ -672,7 +981,7 @@ class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
         )
 
     def test_no_input(self):
-        """ Test flagging an existing PR but without submitting any data. """
+        """Test flagging an existing PR but without submitting any data."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         # No input
@@ -695,8 +1004,7 @@ class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
         )
 
     def test_no_comment(self):
-        """ Test flagging an existing PR but without all the required info.
-        """
+        """Test flagging an existing PR but without all the required info."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         data = {
@@ -729,8 +1037,7 @@ class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
         self.assertEqual(len(request.flags), 0)
 
     def test_invalid_status(self):
-        """ Test flagging an existing PR but with an invalid status.
-        """
+        """Test flagging an existing PR but with an invalid status."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         data = {
@@ -765,7 +1072,7 @@ class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_flag_pr_no_status(self, mock_email):
-        """ Test flagging an existing PR without providing a status.
+        """Test flagging an existing PR without providing a status.
 
         Also check that no notifications have been sent.
         """
@@ -787,22 +1094,22 @@ class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
         data = json.loads(output.get_data(as_text=True))
         data["flag"]["date_created"] = "1510742565"
         data["flag"]["date_updated"] = "1510742565"
-        data["flag"]["pull_request_uid"] = "62b49f00d489452994de5010565fab81"
         data["avatar_url"] = "https://seccdn.libravatar.org/avatar/..."
         self.assertDictEqual(
             data,
             {
                 "flag": {
                     "comment": "Tests failed",
+                    "commit_hash": "hash_commit_stop",
                     "date_created": "1510742565",
                     "date_updated": "1510742565",
                     "percent": 0,
-                    "pull_request_uid": "62b49f00d489452994de5010565fab81",
                     "status": "failure",
                     "url": "http://jenkins.cloud.fedoraproject.org/",
                     "user": {
                         "default_email": "bar@pingou.com",
                         "emails": ["bar@pingou.com", "foo@pingou.com"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -821,16 +1128,20 @@ class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
         request = pagure.lib.query.search_pull_requests(
             self.session, project_id=1, requestid=1
         )
-        self.assertEqual(len(request.flags), 1)
-        self.assertEqual(request.flags[0].comment, "Tests failed")
-        self.assertEqual(request.flags[0].percent, 0)
+        self.assertEqual(len(request.flags), 0)
+
+        flags = pagure.lib.query.get_commit_flag(
+            self.session, request.project, "hash_commit_stop"
+        )
+        self.assertEqual(len(flags), 1)
+        self.assertEqual(flags[0].comment, "Tests failed")
+        self.assertEqual(flags[0].percent, 0)
 
         # no notifications sent
         mock_email.assert_not_called()
 
     def test_editing_flag(self):
-        """ Test flagging an existing PR without providing a status.
-        """
+        """Test flagging an existing PR without providing a status."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         data = {
@@ -849,22 +1160,22 @@ class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
         data = json.loads(output.get_data(as_text=True))
         data["flag"]["date_created"] = "1510742565"
         data["flag"]["date_updated"] = "1510742565"
-        data["flag"]["pull_request_uid"] = "62b49f00d489452994de5010565fab81"
         data["avatar_url"] = "https://seccdn.libravatar.org/avatar/..."
         self.assertDictEqual(
             data,
             {
                 "flag": {
                     "comment": "Tests failed",
+                    "commit_hash": "hash_commit_stop",
                     "date_created": "1510742565",
                     "date_updated": "1510742565",
                     "percent": None,
-                    "pull_request_uid": "62b49f00d489452994de5010565fab81",
                     "status": "failure",
                     "url": "http://jenkins.cloud.fedoraproject.org/",
                     "user": {
                         "default_email": "bar@pingou.com",
                         "emails": ["bar@pingou.com", "foo@pingou.com"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -883,9 +1194,14 @@ class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
         request = pagure.lib.query.search_pull_requests(
             self.session, project_id=1, requestid=1
         )
-        self.assertEqual(len(request.flags), 1)
-        self.assertEqual(request.flags[0].comment, "Tests failed")
-        self.assertEqual(request.flags[0].percent, None)
+        self.assertEqual(len(request.flags), 0)
+
+        flags = pagure.lib.query.get_commit_flag(
+            self.session, request.project, "hash_commit_stop"
+        )
+        self.assertEqual(len(flags), 1)
+        self.assertEqual(flags[0].comment, "Tests failed")
+        self.assertEqual(flags[0].percent, None)
 
         # Update flag
         data = {
@@ -904,22 +1220,22 @@ class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
         data = json.loads(output.get_data(as_text=True))
         data["flag"]["date_created"] = "1510742565"
         data["flag"]["date_updated"] = "1510742565"
-        data["flag"]["pull_request_uid"] = "62b49f00d489452994de5010565fab81"
         data["avatar_url"] = "https://seccdn.libravatar.org/avatar/..."
         self.assertDictEqual(
             data,
             {
                 "flag": {
                     "comment": "Tests passed",
+                    "commit_hash": "hash_commit_stop",
                     "date_created": "1510742565",
                     "date_updated": "1510742565",
                     "percent": 100,
-                    "pull_request_uid": "62b49f00d489452994de5010565fab81",
                     "status": "success",
                     "url": "http://jenkins.cloud.fedoraproject.org/",
                     "user": {
                         "default_email": "bar@pingou.com",
                         "emails": ["bar@pingou.com", "foo@pingou.com"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -938,20 +1254,24 @@ class PagureFlaskApiPRFlagUserTokentests(tests.Modeltests):
         request = pagure.lib.query.search_pull_requests(
             self.session, project_id=1, requestid=1
         )
-        self.assertEqual(len(request.flags), 1)
-        self.assertEqual(request.flags[0].comment, "Tests passed")
-        self.assertEqual(request.flags[0].percent, 100)
+        self.assertEqual(len(request.flags), 0)
+
+        flags = pagure.lib.query.get_commit_flag(
+            self.session, request.project, "hash_commit_stop"
+        )
+        self.assertEqual(len(flags), 1)
+        self.assertEqual(flags[0].comment, "Tests passed")
+        self.assertEqual(flags[0].percent, 100)
 
 
 class PagureFlaskApiGetPRFlagtests(tests.Modeltests):
-    """ Tests for the flask API of pagure for retrieving pull-requests flags
-    """
+    """Tests for the flask API of pagure for retrieving pull-requests flags"""
 
     maxDiff = None
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiGetPRFlagtests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -983,10 +1303,13 @@ class PagureFlaskApiGetPRFlagtests(tests.Modeltests):
         request = pagure.lib.query.search_pull_requests(
             self.session, project_id=1, requestid=1
         )
+        request.commit_stop = "hash_commit_stop"
+        self.session.add(request)
+        self.session.commit()
         self.assertEqual(len(request.flags), 0)
 
     def test_invalid_project(self):
-        """ Test the retrieving the flags of a PR on an invalid project. """
+        """Test the retrieving the flags of a PR on an invalid project."""
 
         # Invalid project
         output = self.app.get("/api/0/foo/pull-request/1/flag")
@@ -997,7 +1320,7 @@ class PagureFlaskApiGetPRFlagtests(tests.Modeltests):
         )
 
     def test_pr_disabled(self):
-        """ Test the retrieving the flags of a PR when PRs are disabled. """
+        """Test the retrieving the flags of a PR when PRs are disabled."""
 
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         settings = repo.settings
@@ -1019,7 +1342,7 @@ class PagureFlaskApiGetPRFlagtests(tests.Modeltests):
         )
 
     def test_no_pr(self):
-        """ Test the retrieving the flags of a PR when the PR doesn't exist. """
+        """Test the retrieving the flags of a PR when the PR doesn't exist."""
 
         # No PR
         output = self.app.get("/api/0/test/pull-request/10/flag")
@@ -1030,7 +1353,7 @@ class PagureFlaskApiGetPRFlagtests(tests.Modeltests):
         )
 
     def test_no_flag(self):
-        """ Test the retrieving the flags of a PR when the PR has no flags. """
+        """Test the retrieving the flags of a PR when the PR has no flags."""
 
         # No flag
         output = self.app.get("/api/0/test/pull-request/1/flag")
@@ -1039,7 +1362,7 @@ class PagureFlaskApiGetPRFlagtests(tests.Modeltests):
         self.assertDictEqual(data, {"flags": []})
 
     def test_get_flag(self):
-        """ Test the retrieving the flags of a PR when the PR has one flag. """
+        """Test the retrieving the flags of a PR when the PR has one flag."""
 
         # Add a flag to the PR
         request = pagure.lib.query.search_pull_requests(
@@ -1060,8 +1383,13 @@ class PagureFlaskApiGetPRFlagtests(tests.Modeltests):
         self.assertEqual(msg, ("Flag added", "jenkins_build_pagure_34"))
         self.session.commit()
 
-        self.assertEqual(len(request.flags), 1)
-        self.assertEqual(request.flags[0].token_id, "aaabbbcccddd")
+        self.assertEqual(len(request.flags), 0)
+
+        flags = pagure.lib.query.get_commit_flag(
+            self.session, request.project, "hash_commit_stop"
+        )
+        self.assertEqual(len(flags), 1)
+        self.assertEqual(flags[0].token_id, "aaabbbcccddd")
 
         # 1 flag
         output = self.app.get("/api/0/test/pull-request/1/flag")
@@ -1069,23 +1397,21 @@ class PagureFlaskApiGetPRFlagtests(tests.Modeltests):
         data = json.loads(output.get_data(as_text=True))
         data["flags"][0]["date_created"] = "1541413645"
         data["flags"][0]["date_updated"] = "1541413645"
-        data["flags"][0][
-            "pull_request_uid"
-        ] = "72a61033c2fc464aa9ef514c057aa62c"
         self.assertDictEqual(
             data,
             {
                 "flags": [
                     {
                         "comment": "Build passes",
+                        "commit_hash": "hash_commit_stop",
                         "date_created": "1541413645",
                         "date_updated": "1541413645",
                         "percent": None,
-                        "pull_request_uid": "72a61033c2fc464aa9ef514c057aa62c",
                         "status": "success",
                         "url": "http://jenkins.cloud.fedoraproject.org",
                         "user": {
                             "fullname": "foo bar",
+                            "full_url": "http://localhost.localdomain/user/foo",
                             "name": "foo",
                             "url_path": "user/foo",
                         },
@@ -1096,7 +1422,7 @@ class PagureFlaskApiGetPRFlagtests(tests.Modeltests):
         )
 
     def test_get_flags(self):
-        """ Test the retrieving the flags of a PR when the PR has one flag. """
+        """Test the retrieving the flags of a PR when the PR has one flag."""
 
         # Add two flags to the PR
         request = pagure.lib.query.search_pull_requests(
@@ -1132,9 +1458,14 @@ class PagureFlaskApiGetPRFlagtests(tests.Modeltests):
         self.assertEqual(msg, ("Flag added", "travis_build_pagure_34"))
         self.session.commit()
 
-        self.assertEqual(len(request.flags), 2)
-        self.assertEqual(request.flags[1].token_id, "aaabbbcccddd")
-        self.assertEqual(request.flags[0].token_id, "aaabbbcccddd")
+        self.assertEqual(len(request.flags), 0)
+
+        flags = pagure.lib.query.get_commit_flag(
+            self.session, request.project, "hash_commit_stop"
+        )
+        self.assertEqual(len(flags), 2)
+        self.assertEqual(flags[1].token_id, "aaabbbcccddd")
+        self.assertEqual(flags[0].token_id, "aaabbbcccddd")
 
         # 1 flag
         output = self.app.get("/api/0/test/pull-request/1/flag")
@@ -1142,47 +1473,43 @@ class PagureFlaskApiGetPRFlagtests(tests.Modeltests):
         data = json.loads(output.get_data(as_text=True))
         data["flags"][0]["date_created"] = "1541413645"
         data["flags"][0]["date_updated"] = "1541413645"
-        data["flags"][0][
-            "pull_request_uid"
-        ] = "72a61033c2fc464aa9ef514c057aa62c"
         data["flags"][1]["date_created"] = "1541413645"
         data["flags"][1]["date_updated"] = "1541413645"
-        data["flags"][1][
-            "pull_request_uid"
-        ] = "72a61033c2fc464aa9ef514c057aa62c"
         self.assertDictEqual(
             data,
             {
                 "flags": [
                     {
-                        "comment": "Build pending",
+                        "comment": "Build passes",
+                        "commit_hash": "hash_commit_stop",
                         "date_created": "1541413645",
                         "date_updated": "1541413645",
                         "percent": None,
-                        "pull_request_uid": "72a61033c2fc464aa9ef514c057aa62c",
-                        "status": "pending",
-                        "url": "http://travis.io",
+                        "status": "success",
+                        "url": "http://jenkins.cloud.fedoraproject.org",
                         "user": {
                             "fullname": "foo bar",
                             "name": "foo",
+                            "full_url": "http://localhost.localdomain/user/foo",
                             "url_path": "user/foo",
                         },
-                        "username": "travis",
+                        "username": "jenkins",
                     },
                     {
-                        "comment": "Build passes",
+                        "comment": "Build pending",
+                        "commit_hash": "hash_commit_stop",
                         "date_created": "1541413645",
                         "date_updated": "1541413645",
                         "percent": None,
-                        "pull_request_uid": "72a61033c2fc464aa9ef514c057aa62c",
-                        "status": "success",
-                        "url": "http://jenkins.cloud.fedoraproject.org",
+                        "status": "pending",
+                        "url": "http://travis.io",
                         "user": {
                             "fullname": "foo bar",
                             "name": "foo",
+                            "full_url": "http://localhost.localdomain/user/foo",
                             "url_path": "user/foo",
                         },
-                        "username": "jenkins",
+                        "username": "travis",
                     },
                 ]
             },
diff --git a/tests/test_pagure_flask_api_project.py b/tests/test_pagure_flask_api_project.py
index 3c364be..8331ce6 100644
--- a/tests/test_pagure_flask_api_project.py
+++ b/tests/test_pagure_flask_api_project.py
@@ -14,6 +14,7 @@ from __future__ import unicode_literals, absolute_import
 import datetime
 import json
 import unittest
+import pagure_messages
 import shutil
 import sys
 import tempfile
@@ -21,7 +22,8 @@ import os
 
 import pygit2
 from celery.result import EagerResult
-from mock import patch, Mock
+from fedora_messaging import api, testing
+from mock import ANY, patch, Mock
 
 sys.path.insert(
     0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
@@ -34,7 +36,7 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureFlaskApiProjecttests(tests.Modeltests):
-    """ Tests for the flask API of pagure for issue """
+    """Tests for the flask API of pagure for issue"""
 
     maxDiff = None
 
@@ -52,7 +54,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         super(PagureFlaskApiProjecttests, self).tearDown()
 
     def test_api_git_tags(self):
-        """ Test the api_git_tags method of the flask api. """
+        """Test the api_git_tags method of the flask api."""
         tests.create_projects(self.session)
 
         # Create a git repo to play with
@@ -120,7 +122,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         shutil.rmtree(newpath)
 
     def test_api_git_branches_no_repo(self):
-        """ Test the api_git_branches method of the flask api when there is no
+        """Test the api_git_branches method of the flask api when there is no
         repo on a project.
         """
         tests.create_projects(self.session)
@@ -128,8 +130,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertEqual(output.status_code, 404)
 
     def test_api_git_urls(self):
-        """ Test the api_project_git_urls method of the flask api.
-        """
+        """Test the api_project_git_urls method of the flask api."""
         tests.create_projects(self.session)
         output = self.app.get("/api/0/test/git/urls")
         self.assertEqual(output.status_code, 200)
@@ -144,7 +145,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_git_urls_no_project(self):
-        """ Test the api_project_git_urls method of the flask api when there is
+        """Test the api_project_git_urls method of the flask api when there is
         no project.
         """
         output = self.app.get("/api/0/test1234/git/urls")
@@ -158,7 +159,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_api_git_urls_private_project(self):
-        """ Test the api_project_git_urls method of the flask api when the
+        """Test the api_project_git_urls method of the flask api when the
         project is private.
         """
         tests.create_projects(self.session)
@@ -185,7 +186,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_api_git_urls_private_project_no_login(self):
-        """ Test the api_project_git_urls method of the flask api when the
+        """Test the api_project_git_urls method of the flask api when the
         project is private and the user is not logged in.
         """
         tests.create_projects(self.session)
@@ -204,7 +205,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_projects_pattern(self):
-        """ Test the api_projects method of the flask api. """
+        """Test the api_projects method of the flask api."""
         tests.create_projects(self.session)
 
         output = self.app.get("/api/0/projects?pattern=test")
@@ -252,6 +253,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "description": "test project #1",
                     "fullname": "test",
                     "url_path": "test",
+                    "full_url": "http://localhost.localdomain/test",
                     "id": 1,
                     "milestones": {},
                     "name": "test",
@@ -262,6 +264,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "user": {
                         "fullname": "PY C",
                         "name": "pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "url_path": "user/pingou",
                     },
                 }
@@ -271,7 +274,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertDictEqual(data, expected_data)
 
     def test_api_projects_pattern_short(self):
-        """ Test the api_projects method of the flask api. """
+        """Test the api_projects method of the flask api."""
         tests.create_projects(self.session)
 
         output = self.app.get("/api/0/projects?pattern=te*&short=1")
@@ -316,7 +319,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertDictEqual(data, expected_data)
 
     def test_api_projects_owner(self):
-        """ Test the api_projects method of the flask api. """
+        """Test the api_projects method of the flask api."""
         tests.create_projects(self.session)
 
         output = self.app.get("/api/0/projects?owner=foo")
@@ -342,7 +345,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertDictEqual(data, expected_data)
 
     def test_api_projects_not_owner(self):
-        """ Test the api_projects method of the flask api. """
+        """Test the api_projects method of the flask api."""
         tests.create_projects(self.session)
 
         output = self.app.get("/api/0/projects?owner=!foo&short=1")
@@ -387,7 +390,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertDictEqual(data, expected_data)
 
     def test_api_projects(self):
-        """ Test the api_projects method of the flask api. """
+        """Test the api_projects method of the flask api."""
         tests.create_projects(self.session)
 
         # Check before adding
@@ -472,6 +475,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "date_created": "1436527638",
                     "date_modified": "1436527638",
                     "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
                     "fullname": "test",
                     "url_path": "test",
                     "id": 1,
@@ -482,8 +486,9 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "priorities": {},
                     "tags": ["infra"],
                     "user": {
-                        "fullname": "PY C",
                         "name": "pingou",
+                        "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "url_path": "user/pingou",
                     },
                 }
@@ -539,6 +544,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "date_created": "1436527638",
                     "date_modified": "1436527638",
                     "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
                     "fullname": "test",
                     "url_path": "test",
                     "id": 1,
@@ -552,6 +558,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                 },
                 {
@@ -579,6 +586,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "date_modified": "1436527638",
                     "description": "test project #2",
                     "fullname": "test2",
+                    "full_url": "http://localhost.localdomain/test2",
                     "url_path": "test2",
                     "id": 2,
                     "milestones": {},
@@ -589,6 +597,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "tags": [],
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -619,6 +628,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "description": "namespaced test project",
                     "fullname": "somenamespace/test3",
                     "url_path": "somenamespace/test3",
+                    "full_url": "http://localhost.localdomain/somenamespace/test3",
                     "id": 3,
                     "milestones": {},
                     "name": "test3",
@@ -628,6 +638,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "tags": [],
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -686,6 +697,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "description": "test project #1",
                     "fullname": "test",
                     "url_path": "test",
+                    "full_url": "http://localhost.localdomain/test",
                     "id": 1,
                     "milestones": {},
                     "name": "test",
@@ -695,6 +707,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "tags": ["infra"],
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -725,6 +738,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "description": "test project #2",
                     "fullname": "test2",
                     "url_path": "test2",
+                    "full_url": "http://localhost.localdomain/test2",
                     "id": 2,
                     "milestones": {},
                     "name": "test2",
@@ -735,6 +749,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "user": {
                         "fullname": "PY C",
                         "name": "pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "url_path": "user/pingou",
                     },
                 },
@@ -764,6 +779,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "description": "namespaced test project",
                     "fullname": "somenamespace/test3",
                     "url_path": "somenamespace/test3",
+                    "full_url": "http://localhost.localdomain/somenamespace/test3",
                     "id": 3,
                     "milestones": {},
                     "name": "test3",
@@ -774,6 +790,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "user": {
                         "fullname": "PY C",
                         "name": "pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "url_path": "user/pingou",
                     },
                 },
@@ -827,6 +844,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "description": "test project #1",
                     "fullname": "test",
                     "url_path": "test",
+                    "full_url": "http://localhost.localdomain/test",
                     "id": 1,
                     "milestones": {},
                     "name": "test",
@@ -837,6 +855,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "user": {
                         "fullname": "PY C",
                         "name": "pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "url_path": "user/pingou",
                     },
                 }
@@ -890,6 +909,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "description": "namespaced test project",
                     "fullname": "somenamespace/test3",
                     "url_path": "somenamespace/test3",
+                    "full_url": "http://localhost.localdomain/somenamespace/test3",
                     "id": 3,
                     "milestones": {},
                     "name": "test3",
@@ -900,6 +920,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "user": {
                         "fullname": "PY C",
                         "name": "pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "url_path": "user/pingou",
                     },
                 }
@@ -909,7 +930,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertDictEqual(data, expected_data)
 
     def test_api_project(self):
-        """ Test the api_project method of the flask api. """
+        """Test the api_project method of the flask api."""
         tests.create_projects(self.session)
 
         # Check before adding
@@ -969,6 +990,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
             "description": "test project #1",
             "fullname": "test",
             "url_path": "test",
+            "full_url": "http://localhost.localdomain/test",
             "id": 1,
             "milestones": {},
             "name": "test",
@@ -979,13 +1001,14 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
             "user": {
                 "fullname": "PY C",
                 "name": "pingou",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "url_path": "user/pingou",
             },
         }
         self.assertDictEqual(data, expected_data)
 
     def test_api_project_collaborators(self):
-        """ Test the api_project method of the flask api. """
+        """Test the api_project method of the flask api."""
         tests.create_projects(self.session)
         tests.create_user(self.session, "ralph", "Ralph B", ["ralph@b.org"])
         tests.create_user(self.session, "nils", "Nils P", ["nils@p.net"])
@@ -1010,6 +1033,27 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         )
         self.session.commit()
 
+        # Add a collaborator group
+        msg = pagure.lib.query.add_group(
+            self.session,
+            group_name="some_group",
+            display_name="Some Group",
+            description=None,
+            group_type="bar",
+            user="pingou",
+            is_admin=False,
+            blacklist=[],
+        )
+        pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="some_group",
+            user="pingou",
+            access="collaborator",
+            branches="features/*",
+        )
+        self.session.commit()
+
         # Existing project
         output = self.app.get("/api/0/test")
         self.assertEqual(output.status_code, 200)
@@ -1019,7 +1063,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         expected_data = {
             "access_groups": {
                 "admin": [],
-                "collaborator": [],
+                "collaborator": ["some_group"],
                 "commit": [],
                 "ticket": [],
             },
@@ -1040,6 +1084,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
             "date_created": "1436527638",
             "date_modified": "1436527638",
             "description": "test project #1",
+            "full_url": "http://localhost.localdomain/test",
             "fullname": "test",
             "url_path": "test",
             "id": 1,
@@ -1053,12 +1098,13 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                 "fullname": "PY C",
                 "name": "pingou",
                 "url_path": "user/pingou",
+                "full_url": "http://localhost.localdomain/user/pingou",
             },
         }
         self.assertDictEqual(data, expected_data)
 
     def test_api_project_group(self):
-        """ Test the api_project method of the flask api. """
+        """Test the api_project method of the flask api."""
         tests.create_projects(self.session)
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
 
@@ -1134,6 +1180,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
             "date_created": "1436527638",
             "date_modified": "1436527638",
             "description": "test project #1",
+            "full_url": "http://localhost.localdomain/test",
             "fullname": "test",
             "url_path": "test",
             "group_details": {"some_group": ["foo"]},
@@ -1148,12 +1195,13 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                 "fullname": "PY C",
                 "name": "pingou",
                 "url_path": "user/pingou",
+                "full_url": "http://localhost.localdomain/user/pingou",
             },
         }
         self.assertDictEqual(data, expected_data)
 
     def test_api_project_group_but_no_group(self):
-        """ Test the api_project method of the flask api when asking for
+        """Test the api_project method of the flask api when asking for
         group details while there are none associated.
         """
         tests.create_projects(self.session)
@@ -1202,6 +1250,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
             "date_created": "1436527638",
             "date_modified": "1436527638",
             "description": "test project #1",
+            "full_url": "http://localhost.localdomain/test",
             "fullname": "test",
             "url_path": "test",
             "id": 1,
@@ -1215,12 +1264,13 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                 "fullname": "PY C",
                 "name": "pingou",
                 "url_path": "user/pingou",
+                "full_url": "http://localhost.localdomain/user/pingou",
             },
         }
         self.assertDictEqual(data, expected_data)
 
     def test_api_projects_pagination(self):
-        """ Test the api_projects method of the flask api with pagination. """
+        """Test the api_projects method of the flask api with pagination."""
         tests.create_projects(self.session)
 
         output = self.app.get("/api/0/projects?page=1")
@@ -1275,6 +1325,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "description": "test project #1",
                     "fullname": "test",
                     "url_path": "test",
+                    "full_url": "http://localhost.localdomain/test",
                     "id": 1,
                     "milestones": {},
                     "name": "test",
@@ -1286,6 +1337,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                 },
                 {
@@ -1314,6 +1366,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "description": "test project #2",
                     "fullname": "test2",
                     "url_path": "test2",
+                    "full_url": "http://localhost.localdomain/test2",
                     "id": 2,
                     "milestones": {},
                     "name": "test2",
@@ -1325,6 +1378,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                 },
                 {
@@ -1353,6 +1407,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "description": "namespaced test project",
                     "fullname": "somenamespace/test3",
                     "url_path": "somenamespace/test3",
+                    "full_url": "http://localhost.localdomain/somenamespace/test3",
                     "id": 3,
                     "milestones": {},
                     "name": "test3",
@@ -1364,6 +1419,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                 },
             ],
@@ -1381,8 +1437,8 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertDictEqual(data, expected_data)
 
     def test_api_projects_pagination_per_page(self):
-        """ Test the api_projects method of the flask api with pagination and
-        the `per_page` argument set. """
+        """Test the api_projects method of the flask api with pagination and
+        the `per_page` argument set."""
         tests.create_projects(self.session)
 
         output = self.app.get("/api/0/projects?page=2&per_page=2")
@@ -1430,6 +1486,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                     "description": "namespaced test project",
                     "fullname": "somenamespace/test3",
                     "url_path": "somenamespace/test3",
+                    "full_url": "http://localhost.localdomain/somenamespace/test3",
                     "id": 3,
                     "milestones": {},
                     "name": "test3",
@@ -1441,6 +1498,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                 }
             ],
@@ -1461,24 +1519,24 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertDictEqual(data, expected_data)
 
     def test_api_projects_pagination_invalid_page(self):
-        """ Test the api_projects method of the flask api when an invalid page
-        value is entered. """
+        """Test the api_projects method of the flask api when an invalid page
+        value is entered."""
         tests.create_projects(self.session)
 
         output = self.app.get("/api/0/projects?page=-3")
         self.assertEqual(output.status_code, 400)
 
     def test_api_projects_pagination_invalid_page_str(self):
-        """ Test the api_projects method of the flask api when an invalid type
-        for the page value is entered. """
+        """Test the api_projects method of the flask api when an invalid type
+        for the page value is entered."""
         tests.create_projects(self.session)
 
         output = self.app.get("/api/0/projects?page=abcd")
         self.assertEqual(output.status_code, 400)
 
     def test_api_projects_pagination_invalid_per_page_too_low(self):
-        """ Test the api_projects method of the flask api when a per_page
-        value is below 1. """
+        """Test the api_projects method of the flask api when a per_page
+        value is below 1."""
         tests.create_projects(self.session)
 
         output = self.app.get("/api/0/projects?page=1&per_page=0")
@@ -1489,8 +1547,8 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         )
 
     def test_api_projects_pagination_invalid_per_page_too_high(self):
-        """ Test the api_projects method of the flask api when a per_page
-        value is above 100. """
+        """Test the api_projects method of the flask api when a per_page
+        value is above 100."""
         tests.create_projects(self.session)
 
         output = self.app.get("/api/0/projects?page=1&per_page=101")
@@ -1501,16 +1559,16 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         )
 
     def test_api_projects_pagination_invalid_per_page_str(self):
-        """ Test the api_projects method of the flask api when an invalid type
-        for the per_page value is entered. """
+        """Test the api_projects method of the flask api when an invalid type
+        for the per_page value is entered."""
         tests.create_projects(self.session)
 
         output = self.app.get("/api/0/projects?page=1&per_page=abcd")
         self.assertEqual(output.status_code, 400)
 
     def test_api_projects_pagination_beyond_last_page(self):
-        """ Test the api_projects method of the flask api when a page value
-        that is larger than the last page is entered. """
+        """Test the api_projects method of the flask api when a page value
+        that is larger than the last page is entered."""
         tests.create_projects(self.session)
 
         output = self.app.get("/api/0/projects?page=99999")
@@ -1554,8 +1612,8 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         )
 
     def test_api_modify_project_main_admin(self):
-        """ Test the api_modify_project method of the flask api when the
-        request is to change the main_admin of the project. """
+        """Test the api_modify_project method of the flask api when the
+        request is to change the main_admin of the project."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
         tests.create_tokens_acl(self.session, "aaabbbcccddd", "modify_project")
@@ -1594,6 +1652,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
             "description": "test project #1",
             "fullname": "test",
             "url_path": "test",
+            "full_url": "http://localhost.localdomain/test",
             "id": 1,
             "milestones": {},
             "name": "test",
@@ -1607,14 +1666,15 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                 "fullname": "foo bar",
                 "name": "foo",
                 "url_path": "user/foo",
+                "full_url": "http://localhost.localdomain/user/foo",
             },
         }
         self.assertEqual(data, expected_output)
 
     def test_api_modify_project_main_admin_retain_access(self):
-        """ Test the api_modify_project method of the flask api when the
+        """Test the api_modify_project method of the flask api when the
         request is to change the main_admin of the project and retain_access
-        is true. """
+        is true."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
         tests.create_tokens_acl(self.session, "aaabbbcccddd", "modify_project")
@@ -1653,6 +1713,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
             "date_created": "1496338274",
             "date_modified": "1496338274",
             "description": "test project #1",
+            "full_url": "http://localhost.localdomain/test",
             "fullname": "test",
             "url_path": "test",
             "id": 1,
@@ -1668,14 +1729,15 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                 "fullname": "foo bar",
                 "name": "foo",
                 "url_path": "user/foo",
+                "full_url": "http://localhost.localdomain/user/foo",
             },
         }
         self.assertEqual(data, expected_output)
 
     def test_api_modify_project_main_admin_retain_access_already_user(self):
-        """ Test the api_modify_project method of the flask api when the
+        """Test the api_modify_project method of the flask api when the
         request is to change the main_admin of the project and retain_access
-        is true and the user becoming the main_admin already has access. """
+        is true and the user becoming the main_admin already has access."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
         tests.create_tokens_acl(self.session, "aaabbbcccddd", "modify_project")
@@ -1724,6 +1786,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
             "date_created": "1496338274",
             "date_modified": "1496338274",
             "description": "test project #1",
+            "full_url": "http://localhost.localdomain/test",
             "fullname": "test",
             "url_path": "test",
             "id": 1,
@@ -1739,13 +1802,14 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                 "fullname": "foo bar",
                 "name": "foo",
                 "url_path": "user/foo",
+                "full_url": "http://localhost.localdomain/user/foo",
             },
         }
         self.assertEqual(data, expected_output)
 
     def test_api_modify_project_main_admin_json(self):
-        """ Test the api_modify_project method of the flask api when the
-        request is to change the main_admin of the project using JSON. """
+        """Test the api_modify_project method of the flask api when the
+        request is to change the main_admin of the project using JSON."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
         tests.create_tokens_acl(self.session, "aaabbbcccddd", "modify_project")
@@ -1789,6 +1853,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
             "description": "test project #1",
             "fullname": "test",
             "url_path": "test",
+            "full_url": "http://localhost.localdomain/test",
             "id": 1,
             "milestones": {},
             "name": "test",
@@ -1802,15 +1867,16 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                 "fullname": "foo bar",
                 "name": "foo",
                 "url_path": "user/foo",
+                "full_url": "http://localhost.localdomain/user/foo",
             },
         }
         self.assertEqual(data, expected_output)
 
     @patch.dict("pagure.config.config", {"PAGURE_ADMIN_USERS": "foo"})
     def test_api_modify_project_main_admin_as_site_admin(self):
-        """ Test the api_modify_project method of the flask api when the
+        """Test the api_modify_project method of the flask api when the
         request is to change the main_admin of the project and the user is a
-        Pagure site admin. """
+        Pagure site admin."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session, user_id=2, project_id=None)
         tests.create_tokens_acl(self.session, "aaabbbcccddd", "modify_project")
@@ -1852,6 +1918,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
             "date_created": "1496338274",
             "date_modified": "1496338274",
             "description": "test project #1",
+            "full_url": "http://localhost.localdomain/test",
             "fullname": "test",
             "url_path": "test",
             "id": 1,
@@ -1867,6 +1934,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
                 "fullname": "foo bar",
                 "name": "foo",
                 "url_path": "user/foo",
+                "full_url": "http://localhost.localdomain/user/foo",
             },
         }
         self.assertEqual(data, expected_output)
@@ -1877,7 +1945,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertNotEqual(date_before, project.date_modified)
 
     def test_api_modify_project_main_admin_not_main_admin(self):
-        """ Test the api_modify_project method of the flask api when the
+        """Test the api_modify_project method of the flask api when the
         requester is not the main_admin of the project and requests to change
         the main_admin.
         """
@@ -1906,7 +1974,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         )
 
     def test_api_modify_project_not_admin(self):
-        """ Test the api_modify_project method of the flask api when the
+        """Test the api_modify_project method of the flask api when the
         requester is not an admin of the project.
         """
         tests.create_projects(self.session)
@@ -1927,7 +1995,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         )
 
     def test_api_modify_project_invalid_request(self):
-        """ Test the api_modify_project method of the flask api when the
+        """Test the api_modify_project method of the flask api when the
         request data is invalid.
         """
         tests.create_projects(self.session)
@@ -1946,7 +2014,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         )
 
     def test_api_modify_project_invalid_keys(self):
-        """ Test the api_modify_project method of the flask api when the
+        """Test the api_modify_project method of the flask api when the
         request data contains an invalid key.
         """
         tests.create_projects(self.session)
@@ -1967,7 +2035,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         )
 
     def test_api_modify_project_invalid_new_main_admin(self):
-        """ Test the api_modify_project method of the flask api when the
+        """Test the api_modify_project method of the flask api when the
         request is to change the main_admin of the project to a main_admin
         that doesn't exist.
         """
@@ -1989,7 +2057,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         )
 
     def test_api_project_watchers(self):
-        """ Test the api_project_watchers method of the flask api. """
+        """Test the api_project_watchers method of the flask api."""
         tests.create_projects(self.session)
         # The user is not logged in and the owner is watching issues implicitly
         output = self.app.get("/api/0/test/watchers")
@@ -2198,7 +2266,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         },
     )
     def test_adopt_repos(self):
-        """ Test the new_project endpoint with existing git repo. """
+        """Test the new_project endpoint with existing git repo."""
         # Before
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 0)
@@ -2248,7 +2316,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
             self.assertDictEqual(data, {"message": 'Project "test" created'})
 
     def test_api_fork_project(self):
-        """ Test the api_fork_project method of the flask api. """
+        """Test the api_fork_project method of the flask api."""
         tests.create_projects(self.session)
         for folder in ["docs", "tickets", "requests", "repos"]:
             tests.create_projects_git(
@@ -2351,7 +2419,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         )
 
     def test_api_fork_project_user_token(self):
-        """ Test the api_fork_project method of the flask api. """
+        """Test the api_fork_project method of the flask api."""
         tests.create_projects(self.session)
         for folder in ["docs", "tickets", "requests", "repos"]:
             tests.create_projects_git(
@@ -2454,7 +2522,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         )
 
     def test_api_generate_acls(self):
-        """ Test the api_generate_acls method of the flask api """
+        """Test the api_generate_acls method of the flask api"""
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
         tests.create_tokens_acl(
@@ -2480,7 +2548,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         )
 
     def test_api_generate_acls_json(self):
-        """ Test the api_generate_acls method of the flask api using JSON """
+        """Test the api_generate_acls method of the flask api using JSON"""
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
         tests.create_tokens_acl(
@@ -2510,8 +2578,8 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         )
 
     def test_api_generate_acls_wait_true(self):
-        """ Test the api_generate_acls method of the flask api when wait is
-        set to True """
+        """Test the api_generate_acls method of the flask api when wait is
+        set to True"""
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
         tests.create_tokens_acl(
@@ -2539,8 +2607,8 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertTrue(task_result.get.called)
 
     def test_api_generate_acls_no_project(self):
-        """ Test the api_generate_acls method of the flask api when the project
-        doesn't exist """
+        """Test the api_generate_acls method of the flask api when the project
+        doesn't exist"""
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
         tests.create_tokens_acl(
@@ -2563,7 +2631,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_api_new_git_branch(self):
-        """ Test the api_new_branch method of the flask api """
+        """Test the api_new_branch method of the flask api"""
         tests.create_projects(self.session)
         repo_path = os.path.join(self.path, "repos")
         tests.create_projects_git(repo_path, bare=True)
@@ -2584,7 +2652,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertIn("test123", repo_obj.listall_branches())
 
     def test_api_new_git_branch_json(self):
-        """ Test the api_new_branch method of the flask api """
+        """Test the api_new_branch method of the flask api"""
         tests.create_projects(self.session)
         repo_path = os.path.join(self.path, "repos")
         tests.create_projects_git(repo_path, bare=True)
@@ -2608,7 +2676,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertIn("test123", repo_obj.listall_branches())
 
     def test_api_new_git_branch_from_branch(self):
-        """ Test the api_new_branch method of the flask api """
+        """Test the api_new_branch method of the flask api"""
         tests.create_projects(self.session)
         repo_path = os.path.join(self.path, "repos")
         tests.create_projects_git(repo_path, bare=True)
@@ -2631,8 +2699,8 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertIn("test123", repo_obj.listall_branches())
 
     def test_api_new_git_branch_already_exists(self):
-        """ Test the api_new_branch method of the flask api when branch already
-        exists """
+        """Test the api_new_branch method of the flask api when branch already
+        exists"""
         tests.create_projects(self.session)
         repo_path = os.path.join(self.path, "repos")
         tests.create_projects_git(repo_path, bare=True)
@@ -2653,7 +2721,7 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_api_new_git_branch_from_commit(self):
-        """ Test the api_new_branch method of the flask api """
+        """Test the api_new_branch method of the flask api"""
         tests.create_projects(self.session)
         repos_path = os.path.join(self.path, "repos")
         tests.create_projects_git(repos_path, bare=True)
@@ -2676,11 +2744,10 @@ class PagureFlaskApiProjecttests(tests.Modeltests):
 
 
 class PagureFlaskApiProjectFlagtests(tests.Modeltests):
-    """ Tests for the flask API of pagure for flagging commit in project
-    """
+    """Tests for the flask API of pagure for flagging commit in project"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiProjectFlagtests, self).setUp()
 
         tests.create_projects(self.session)
@@ -2692,7 +2759,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
         tests.create_tokens_acl(self.session, "aaabbbcccddd", "commit_flag")
 
     def test_flag_commit_missing_status(self):
-        """ Test flagging a commit with missing precentage. """
+        """Test flagging a commit with missing precentage."""
         repo_obj = pygit2.Repository(self.git_path)
         commit = repo_obj.revparse_single("HEAD")
 
@@ -2720,7 +2787,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_flag_commit_missing_username(self):
-        """ Test flagging a commit with missing username. """
+        """Test flagging a commit with missing username."""
         repo_obj = pygit2.Repository(self.git_path)
         commit = repo_obj.revparse_single("HEAD")
 
@@ -2747,7 +2814,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_flag_commit_missing_comment(self):
-        """ Test flagging a commit with missing comment. """
+        """Test flagging a commit with missing comment."""
         repo_obj = pygit2.Repository(self.git_path)
         commit = repo_obj.revparse_single("HEAD")
 
@@ -2774,7 +2841,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_flag_commit_missing_url(self):
-        """ Test flagging a commit with missing url. """
+        """Test flagging a commit with missing url."""
         repo_obj = pygit2.Repository(self.git_path)
         commit = repo_obj.revparse_single("HEAD")
 
@@ -2801,7 +2868,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_flag_commit_invalid_token(self):
-        """ Test flagging a commit with missing info. """
+        """Test flagging a commit with missing info."""
         repo_obj = pygit2.Repository(self.git_path)
         commit = repo_obj.revparse_single("HEAD")
 
@@ -2830,7 +2897,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
         self.assertEqual(data["errors"], "Invalid token")
 
     def test_flag_commit_invalid_status(self):
-        """ Test flagging a commit with an invalid status. """
+        """Test flagging a commit with an invalid status."""
         repo_obj = pygit2.Repository(self.git_path)
         commit = repo_obj.revparse_single("HEAD")
 
@@ -2859,7 +2926,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
         )
 
     def test_flag_commit_with_uid(self):
-        """ Test flagging a commit with provided uid. """
+        """Test flagging a commit with provided uid."""
         repo_obj = pygit2.Repository(self.git_path)
         commit = repo_obj.revparse_single("HEAD")
 
@@ -2894,6 +2961,123 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
                 "user": {
                     "default_email": "bar@pingou.com",
                     "emails": ["bar@pingou.com", "foo@pingou.com"],
+                    "full_url": "http://localhost.localdomain/user/pingou",
+                    "fullname": "PY C",
+                    "name": "pingou",
+                    "url_path": "user/pingou",
+                },
+                "username": "Jenkins",
+            },
+            "message": "Flag added",
+            "uid": "jenkins_build_pagure_100+seed",
+        }
+
+        self.assertEqual(data, expected_output)
+
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
+    def test_update_flag_commit_with_uid(self):
+        """Test flagging a commit with provided uid."""
+        repo_obj = pygit2.Repository(self.git_path)
+        commit = repo_obj.revparse_single("HEAD")
+
+        headers = {"Authorization": "token aaabbbcccddd"}
+        data = {
+            "username": "Jenkins",
+            "percent": 0,
+            "comment": "Tests running",
+            "url": "http://jenkins.cloud.fedoraproject.org/",
+            "uid": "jenkins_build_pagure_100+seed",
+            "status": "pending",
+        }
+        with testing.mock_sends(
+            pagure_messages.CommitFlagAddedV1(
+                topic="pagure.commit.flag.added",
+                body={
+                    "repo": {
+                        "id": 1,
+                        "name": "test",
+                        "fullname": "test",
+                        "url_path": "test",
+                        "description": "test project #1",
+                        "full_url": "http://localhost.localdomain/test",
+                        "namespace": None,
+                        "parent": None,
+                        "date_created": ANY,
+                        "date_modified": ANY,
+                        "user": {
+                            "name": "pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                        },
+                        "access_users": {
+                            "owner": ["pingou"],
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "access_groups": {
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "tags": [],
+                        "priorities": {},
+                        "custom_keys": [],
+                        "close_status": [
+                            "Invalid",
+                            "Insufficient data",
+                            "Fixed",
+                            "Duplicate",
+                        ],
+                        "milestones": {},
+                    },
+                    "flag": {
+                        "commit_hash": commit.oid.hex,
+                        "username": "Jenkins",
+                        "percent": "0",
+                        "comment": "Tests running",
+                        "status": "pending",
+                        "url": "http://jenkins.cloud.fedoraproject.org/",
+                        "date_created": ANY,
+                        "date_updated": ANY,
+                        "user": {
+                            "name": "pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                        },
+                    },
+                    "agent": "pingou",
+                },
+            )
+        ):
+            output = self.app.post(
+                "/api/0/test/c/%s/flag" % commit.oid.hex,
+                headers=headers,
+                data=data,
+            )
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        data["flag"]["date_created"] = "1510742565"
+        data["flag"]["date_updated"] = "1510742565"
+        expected_output = {
+            "flag": {
+                "comment": "Tests running",
+                "commit_hash": commit.oid.hex,
+                "date_created": "1510742565",
+                "date_updated": "1510742565",
+                "percent": 0,
+                "status": "pending",
+                "url": "http://jenkins.cloud.fedoraproject.org/",
+                "user": {
+                    "default_email": "bar@pingou.com",
+                    "emails": ["bar@pingou.com", "foo@pingou.com"],
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "fullname": "PY C",
                     "name": "pingou",
                     "url_path": "user/pingou",
@@ -2906,9 +3090,116 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
 
         self.assertEqual(data, expected_output)
 
+        data = {
+            "username": "Jenkins",
+            "percent": 100,
+            "comment": "Tests passed",
+            "url": "http://jenkins.cloud.fedoraproject.org/",
+            "uid": "jenkins_build_pagure_100+seed",
+            "status": "success",
+        }
+        with testing.mock_sends(
+            pagure_messages.CommitFlagUpdatedV1(
+                topic="pagure.commit.flag.updated",
+                body={
+                    "repo": {
+                        "id": 1,
+                        "name": "test",
+                        "fullname": "test",
+                        "url_path": "test",
+                        "description": "test project #1",
+                        "full_url": "http://localhost.localdomain/test",
+                        "namespace": None,
+                        "parent": None,
+                        "date_created": ANY,
+                        "date_modified": ANY,
+                        "user": {
+                            "name": "pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                        },
+                        "access_users": {
+                            "owner": ["pingou"],
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "access_groups": {
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "tags": [],
+                        "priorities": {},
+                        "custom_keys": [],
+                        "close_status": [
+                            "Invalid",
+                            "Insufficient data",
+                            "Fixed",
+                            "Duplicate",
+                        ],
+                        "milestones": {},
+                    },
+                    "flag": {
+                        "commit_hash": commit.oid.hex,
+                        "username": "Jenkins",
+                        "percent": "100",
+                        "comment": "Tests passed",
+                        "status": "success",
+                        "url": "http://jenkins.cloud.fedoraproject.org/",
+                        "date_created": ANY,
+                        "date_updated": ANY,
+                        "user": {
+                            "name": "pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                        },
+                    },
+                    "agent": "pingou",
+                },
+            )
+        ):
+            output = self.app.post(
+                "/api/0/test/c/%s/flag" % commit.oid.hex,
+                headers=headers,
+                data=data,
+            )
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        data["flag"]["date_created"] = "1510742565"
+        data["flag"]["date_updated"] = "1510742565"
+        expected_output = {
+            "flag": {
+                "comment": "Tests passed",
+                "commit_hash": commit.oid.hex,
+                "date_created": "1510742565",
+                "date_updated": "1510742565",
+                "percent": 100,
+                "status": "success",
+                "url": "http://jenkins.cloud.fedoraproject.org/",
+                "user": {
+                    "default_email": "bar@pingou.com",
+                    "emails": ["bar@pingou.com", "foo@pingou.com"],
+                    "full_url": "http://localhost.localdomain/user/pingou",
+                    "fullname": "PY C",
+                    "name": "pingou",
+                    "url_path": "user/pingou",
+                },
+                "username": "Jenkins",
+            },
+            "message": "Flag updated",
+            "uid": "jenkins_build_pagure_100+seed",
+        }
+
+        self.assertEqual(data, expected_output)
+
     @patch("pagure.lib.notify.send_email")
     def test_flag_commit_without_uid(self, mock_email):
-        """ Test flagging a commit with missing info.
+        """Test flagging a commit with missing info.
 
         Also ensure notifications aren't sent when they are not asked for.
         """
@@ -2946,6 +3237,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
                 "user": {
                     "default_email": "bar@pingou.com",
                     "emails": ["bar@pingou.com", "foo@pingou.com"],
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "fullname": "PY C",
                     "name": "pingou",
                     "url_path": "user/pingou",
@@ -2961,7 +3253,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_flag_commit_with_notification(self, mock_email):
-        """ Test flagging a commit with notification enabled. """
+        """Test flagging a commit with notification enabled."""
 
         # Enable commit notifications
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -2982,6 +3274,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
             "url": "http://jenkins.cloud.fedoraproject.org/",
             "status": "success",
         }
+
         output = self.app.post(
             "/api/0/test/c/%s/flag" % commit.oid.hex,
             headers=headers,
@@ -3005,6 +3298,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
                 "user": {
                     "default_email": "bar@pingou.com",
                     "emails": ["bar@pingou.com", "foo@pingou.com"],
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "fullname": "PY C",
                     "name": "pingou",
                     "url_path": "user/pingou",
@@ -3044,8 +3338,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
         },
     )
     def test_flag_commit_with_custom_flags(self):
-        """ Test flagging when custom flags are set up
-        """
+        """Test flagging when custom flags are set up"""
         repo_obj = pygit2.Repository(self.git_path)
         commit = repo_obj.revparse_single("HEAD")
 
@@ -3085,7 +3378,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
         )
 
     def test_commit_flags(self):
-        """ Test retrieving commit flags. """
+        """Test retrieving commit flags."""
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         repo_obj = pygit2.Repository(self.git_path)
         commit = repo_obj.revparse_single("HEAD")
@@ -3147,6 +3440,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
                     "url": "https://koji.fp.o/koji...",
                     "user": {
                         "fullname": "foo bar",
+                        "full_url": "http://localhost.localdomain/user/foo",
                         "name": "foo",
                         "url_path": "user/foo",
                     },
@@ -3162,6 +3456,7 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
                     "url": "https://koji.fp.o/koji...",
                     "user": {
                         "fullname": "foo bar",
+                        "full_url": "http://localhost.localdomain/user/foo",
                         "name": "foo",
                         "url_path": "user/foo",
                     },
@@ -3175,13 +3470,12 @@ class PagureFlaskApiProjectFlagtests(tests.Modeltests):
 
 
 class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
-    """ Tests for the flask API of pagure for modifying ACLs in a project
-    """
+    """Tests for the flask API of pagure for modifying ACLs in a project"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiProjectModifyAclTests, self).setUp()
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
@@ -3194,8 +3488,8 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         )
 
     def test_api_modify_acls_no_project(self):
-        """ Test the api_modify_acls method of the flask api when the project
-        doesn't exist """
+        """Test the api_modify_acls method of the flask api when the project
+        doesn't exist"""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         data = {"user_type": "user", "name": "bar", "acl": "commit"}
@@ -3211,8 +3505,8 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_api_modify_acls_no_user(self):
-        """ Test the api_modify_acls method of the flask api when the user
-        doesn't exist """
+        """Test the api_modify_acls method of the flask api when the user
+        doesn't exist"""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         data = {"user_type": "user", "name": "nosuchuser", "acl": "commit"}
@@ -3228,8 +3522,8 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_api_modify_acls_no_group(self):
-        """ Test the api_modify_acls method of the flask api when the group
-        doesn't exist """
+        """Test the api_modify_acls method of the flask api when the group
+        doesn't exist"""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         data = {"user_type": "group", "name": "nosuchgroup", "acl": "commit"}
@@ -3245,8 +3539,8 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_api_modify_acls_no_permission(self):
-        """ Test the api_modify_acls method of the flask api when the user
-        doesn't have permissions """
+        """Test the api_modify_acls method of the flask api when the user
+        doesn't have permissions"""
         item = pagure.lib.model.Token(
             id="foo_token2",
             user_id=2,
@@ -3273,8 +3567,8 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_api_modify_acls_neither_user_nor_group(self):
-        """ Test the api_modify_acls method of the flask api when neither
-        user nor group was set """
+        """Test the api_modify_acls method of the flask api when neither
+        user nor group was set"""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         data = {"acl": "commit"}
@@ -3298,8 +3592,8 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_api_modify_acls_invalid_acl(self):
-        """ Test the api_modify_acls method of the flask api when the ACL
-        doesn't exist. Must be one of ticket, commit or admin. """
+        """Test the api_modify_acls method of the flask api when the ACL
+        doesn't exist. Must be one of ticket, commit or admin."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         data = {"user_type": "user", "name": "bar", "acl": "invalidacl"}
@@ -3316,8 +3610,8 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_api_modify_acls_user(self):
-        """ Test the api_modify_acls method of the flask api for
-        setting an ACL for a user. """
+        """Test the api_modify_acls method of the flask api for
+        setting an ACL for a user."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         data = {"user_type": "user", "name": "foo", "acl": "commit"}
@@ -3353,6 +3647,7 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
             "date_created": "1510742565",
             "date_modified": "1510742566",
             "description": "test project #1",
+            "full_url": "http://localhost.localdomain/test",
             "fullname": "test",
             "id": 1,
             "milestones": {},
@@ -3364,6 +3659,7 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
             "url_path": "test",
             "user": {
                 "fullname": "PY C",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "name": "pingou",
                 "url_path": "user/pingou",
             },
@@ -3371,8 +3667,8 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_api_modify_acls_group(self):
-        """ Test the api_modify_acls method of the flask api for
-        setting an ACL for a group. """
+        """Test the api_modify_acls method of the flask api for
+        setting an ACL for a group."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         # Create a group
@@ -3423,6 +3719,7 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
             "date_created": "1510742565",
             "date_modified": "1510742566",
             "description": "test project #1",
+            "full_url": "http://localhost.localdomain/test",
             "fullname": "test",
             "id": 1,
             "milestones": {},
@@ -3435,14 +3732,15 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
             "user": {
                 "fullname": "PY C",
                 "name": "pingou",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "url_path": "user/pingou",
             },
         }
         self.assertEqual(data, expected_output)
 
     def test_api_modify_acls_no_acl(self):
-        """ Test the api_modify_acls method of the flask api when no ACL
-        are specified. """
+        """Test the api_modify_acls method of the flask api when no ACL
+        are specified."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         project = pagure.lib.query._get_project(self.session, "test")
@@ -3466,9 +3764,9 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_api_modify_acls_remove_own_acl_no_access(self):
-        """ Test the api_modify_acls method of the flask api when no ACL
+        """Test the api_modify_acls method of the flask api when no ACL
         are specified, so the user tries to remove their own access but the
-        user is the project owner. """
+        user is the project owner."""
         headers = {"Authorization": "token aaabbbcccddd"}
 
         data = {"user_type": "user", "name": "pingou"}
@@ -3486,9 +3784,9 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_api_modify_acls_remove_own_acl_(self):
-        """ Test the api_modify_acls method of the flask api when no ACL
+        """Test the api_modify_acls method of the flask api when no ACL
         are specified, so the user tries to remove their own access but the
-        user is the project owner. """
+        user is the project owner."""
         # Add the user `foo` to the project
         self.test_api_modify_acls_user()
 
@@ -3554,6 +3852,7 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
             "date_created": "1510742565",
             "date_modified": "1510742566",
             "description": "test project #1",
+            "full_url": "http://localhost.localdomain/test",
             "fullname": "test",
             "id": 1,
             "milestones": {},
@@ -3566,6 +3865,7 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
             "user": {
                 "fullname": "PY C",
                 "name": "pingou",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "url_path": "user/pingou",
             },
         }
@@ -3581,8 +3881,8 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         )
 
     def test_api_modify_acls_remove_someone_else_acl(self):
-        """ Test the api_modify_acls method of the flask api an admin tries
-        to remove access from someone else. """
+        """Test the api_modify_acls method of the flask api an admin tries
+        to remove access from someone else."""
         # Add the user `foo` to the project
         self.test_api_modify_acls_user()
 
@@ -3633,6 +3933,7 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
             "date_created": "1510742565",
             "date_modified": "1510742566",
             "description": "test project #1",
+            "full_url": "http://localhost.localdomain/test",
             "fullname": "test",
             "id": 1,
             "milestones": {},
@@ -3645,6 +3946,7 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
             "user": {
                 "fullname": "PY C",
                 "name": "pingou",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "url_path": "user/pingou",
             },
         }
@@ -3659,15 +3961,251 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
             {"admin": [], "collaborator": [], "commit": [], "ticket": []},
         )
 
+    def test_api_modify_acls_add_remove_group(self):
+        """Test the api_modify_acls method of the flask api for
+        setting an ACL for a group."""
+        headers = {"Authorization": "token aaabbbcccddd"}
+
+        # Create a group
+        msg = pagure.lib.query.add_group(
+            self.session,
+            group_name="baz",
+            display_name="baz group",
+            description=None,
+            group_type="bar",
+            user="foo",
+            is_admin=False,
+            blacklist=[],
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User `foo` added to the group `baz`.")
+
+        # Add the group to the project
+        data = {"user_type": "group", "name": "baz", "acl": "ticket"}
+        output = self.app.post(
+            "/api/0/test/git/modifyacls", headers=headers, data=data
+        )
+
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        data["date_created"] = "1510742565"
+        data["date_modified"] = "1510742566"
+
+        expected_output = {
+            "access_groups": {
+                "admin": [],
+                "collaborator": [],
+                "commit": [],
+                "ticket": ["baz"],
+            },
+            "access_users": {
+                "admin": [],
+                "collaborator": [],
+                "commit": [],
+                "owner": ["pingou"],
+                "ticket": [],
+            },
+            "close_status": [
+                "Invalid",
+                "Insufficient data",
+                "Fixed",
+                "Duplicate",
+            ],
+            "custom_keys": [],
+            "date_created": "1510742565",
+            "date_modified": "1510742566",
+            "description": "test project #1",
+            "fullname": "test",
+            "full_url": "http://localhost.localdomain/test",
+            "id": 1,
+            "milestones": {},
+            "name": "test",
+            "namespace": None,
+            "parent": None,
+            "priorities": {},
+            "tags": [],
+            "url_path": "test",
+            "user": {
+                "fullname": "PY C",
+                "full_url": "http://localhost.localdomain/user/pingou",
+                "name": "pingou",
+                "url_path": "user/pingou",
+            },
+        }
+        self.assertEqual(data, expected_output)
+
+        # Ensure `baz` was properly added
+        self.session = pagure.lib.query.create_session(self.dbpath)
+        project = pagure.lib.query._get_project(self.session, "test")
+        self.assertEquals(
+            project.access_users,
+            {"admin": [], "collaborator": [], "commit": [], "ticket": []},
+        )
+        self.assertNotEquals(
+            project.access_groups,
+            {"admin": [], "collaborator": [], "commit": [], "ticket": []},
+        )
+        self.assertEquals(len(project.access_groups["ticket"]), 1)
+
+        # Remove the group from the project
+        data = {"user_type": "group", "name": "baz", "acl": None}
+        output = self.app.post(
+            "/api/0/test/git/modifyacls", headers=headers, data=data
+        )
+
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        data["date_created"] = "1510742565"
+        data["date_modified"] = "1510742566"
+
+        expected_output = {
+            "access_groups": {
+                "admin": [],
+                "collaborator": [],
+                "commit": [],
+                "ticket": [],
+            },
+            "access_users": {
+                "admin": [],
+                "collaborator": [],
+                "commit": [],
+                "owner": ["pingou"],
+                "ticket": [],
+            },
+            "close_status": [
+                "Invalid",
+                "Insufficient data",
+                "Fixed",
+                "Duplicate",
+            ],
+            "custom_keys": [],
+            "date_created": "1510742565",
+            "date_modified": "1510742566",
+            "description": "test project #1",
+            "full_url": "http://localhost.localdomain/test",
+            "fullname": "test",
+            "id": 1,
+            "milestones": {},
+            "name": "test",
+            "namespace": None,
+            "parent": None,
+            "priorities": {},
+            "tags": [],
+            "url_path": "test",
+            "user": {
+                "fullname": "PY C",
+                "name": "pingou",
+                "full_url": "http://localhost.localdomain/user/pingou",
+                "url_path": "user/pingou",
+            },
+        }
+        self.assertEqual(data, expected_output)
+
+        # Ensure `baz` was properly removed
+        self.session = pagure.lib.query.create_session(self.dbpath)
+        project = pagure.lib.query._get_project(self.session, "test")
+        self.assertEquals(
+            project.access_users,
+            {"admin": [], "collaborator": [], "commit": [], "ticket": []},
+        )
+        self.assertEquals(
+            project.access_groups,
+            {"admin": [], "collaborator": [], "commit": [], "ticket": []},
+        )
+
+    def test_api_modify_acls_remove_group_not_in_project(self):
+        """Test the api_modify_acls method of the flask api for
+        setting an ACL for a group."""
+        headers = {"Authorization": "token aaabbbcccddd"}
+
+        # Create a group
+        msg = pagure.lib.query.add_group(
+            self.session,
+            group_name="baz",
+            display_name="baz group",
+            description=None,
+            group_type="bar",
+            user="foo",
+            is_admin=False,
+            blacklist=[],
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User `foo` added to the group `baz`.")
+
+        # Remove the group from the project
+        data = {"user_type": "group", "name": "baz", "acl": None}
+        output = self.app.post(
+            "/api/0/test/git/modifyacls", headers=headers, data=data
+        )
+
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        data["date_created"] = "1510742565"
+        data["date_modified"] = "1510742566"
+
+        expected_output = {
+            "access_groups": {
+                "admin": [],
+                "collaborator": [],
+                "commit": [],
+                "ticket": [],
+            },
+            "access_users": {
+                "admin": [],
+                "collaborator": [],
+                "commit": [],
+                "owner": ["pingou"],
+                "ticket": [],
+            },
+            "close_status": [
+                "Invalid",
+                "Insufficient data",
+                "Fixed",
+                "Duplicate",
+            ],
+            "custom_keys": [],
+            "date_created": "1510742565",
+            "date_modified": "1510742566",
+            "description": "test project #1",
+            "full_url": "http://localhost.localdomain/test",
+            "fullname": "test",
+            "id": 1,
+            "milestones": {},
+            "name": "test",
+            "namespace": None,
+            "parent": None,
+            "priorities": {},
+            "tags": [],
+            "url_path": "test",
+            "user": {
+                "fullname": "PY C",
+                "name": "pingou",
+                "url_path": "user/pingou",
+                "full_url": "http://localhost.localdomain/user/pingou",
+            },
+        }
+        self.assertEqual(data, expected_output)
+
+        # Ensure `baz` was properly removed
+        self.session = pagure.lib.query.create_session(self.dbpath)
+        project = pagure.lib.query._get_project(self.session, "test")
+        self.assertEquals(
+            project.access_users,
+            {"admin": [], "collaborator": [], "commit": [], "ticket": []},
+        )
+        self.assertEquals(
+            project.access_groups,
+            {"admin": [], "collaborator": [], "commit": [], "ticket": []},
+        )
+
 
 class PagureFlaskApiProjectOptionsTests(tests.Modeltests):
-    """ Tests for the flask API of pagure for modifying options ofs a project
-    """
+    """Tests for the flask API of pagure for modifying options ofs a project"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiProjectOptionsTests, self).setUp()
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
@@ -3680,7 +4218,7 @@ class PagureFlaskApiProjectOptionsTests(tests.Modeltests):
         )
 
     def test_api_get_project_options_wrong_project(self):
-        """ Test accessing api_get_project_options w/o auth header. """
+        """Test accessing api_get_project_options w/o auth header."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         output = self.app.get("/api/0/unknown/options", headers=headers)
@@ -3691,7 +4229,7 @@ class PagureFlaskApiProjectOptionsTests(tests.Modeltests):
         )
 
     def test_api_get_project_options_wo_header(self):
-        """ Test accessing api_get_project_options w/o auth header. """
+        """Test accessing api_get_project_options w/o auth header."""
 
         output = self.app.get("/api/0/test/options")
         self.assertEqual(output.status_code, 401)
@@ -3708,7 +4246,7 @@ class PagureFlaskApiProjectOptionsTests(tests.Modeltests):
         )
 
     def test_api_get_project_options_w_header(self):
-        """ Test accessing api_get_project_options w/ auth header. """
+        """Test accessing api_get_project_options w/ auth header."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         output = self.app.get("/api/0/test/options", headers=headers)
@@ -3742,8 +4280,7 @@ class PagureFlaskApiProjectOptionsTests(tests.Modeltests):
         )
 
     def test_api_modify_project_options_wrong_project(self):
-        """ Test accessing api_modify_project_options w/ an invalid project.
-        """
+        """Test accessing api_modify_project_options w/ an invalid project."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         output = self.app.post(
@@ -3756,7 +4293,7 @@ class PagureFlaskApiProjectOptionsTests(tests.Modeltests):
         )
 
     def test_api_modify_project_options_wo_header(self):
-        """ Test accessing api_modify_project_options w/o auth header. """
+        """Test accessing api_modify_project_options w/o auth header."""
 
         output = self.app.post("/api/0/test/options/update")
         self.assertEqual(output.status_code, 401)
@@ -3773,7 +4310,7 @@ class PagureFlaskApiProjectOptionsTests(tests.Modeltests):
         )
 
     def test_api_modify_project_options_no_data(self):
-        """ Test accessing api_modify_project_options w/ auth header. """
+        """Test accessing api_modify_project_options w/ auth header."""
 
         # check before
         headers = {"Authorization": "token aaabbbcccddd"}
@@ -3826,7 +4363,7 @@ class PagureFlaskApiProjectOptionsTests(tests.Modeltests):
         self.assertEqual(after, before)
 
     def test_api_modify_project_options(self):
-        """ Test accessing api_modify_project_options w/ auth header. """
+        """Test accessing api_modify_project_options w/ auth header."""
 
         # check before
         headers = {"Authorization": "token aaabbbcccddd"}
@@ -3884,22 +4421,141 @@ class PagureFlaskApiProjectOptionsTests(tests.Modeltests):
         before["settings"]["issues_default_to_private"] = True
         self.assertEqual(after, before)
 
+    def test_api_modify_project_options2(self):
+        """Test accessing api_modify_project_options w/ auth header."""
+
+        # check before
+        headers = {"Authorization": "token aaabbbcccddd"}
+        output = self.app.get("/api/0/test/options", headers=headers)
+        self.assertEqual(output.status_code, 200)
+        before = json.loads(output.get_data(as_text=True))
+        self.assertEqual(
+            before,
+            {
+                "settings": {
+                    "Enforce_signed-off_commits_in_pull-request": False,
+                    "Minimum_score_to_merge_pull-request": -1,
+                    "Only_assignee_can_merge_pull-request": False,
+                    "Web-hooks": None,
+                    "always_merge": False,
+                    "disable_non_fast-forward_merges": False,
+                    "fedmsg_notifications": True,
+                    "issue_tracker": True,
+                    "issue_tracker_read_only": False,
+                    "issues_default_to_private": False,
+                    "mqtt_notifications": True,
+                    "notify_on_commit_flag": False,
+                    "notify_on_pull-request_flag": False,
+                    "open_metadata_access_to_all": False,
+                    "project_documentation": False,
+                    "pull_request_access_only": False,
+                    "pull_requests": True,
+                    "stomp_notifications": True,
+                },
+                "status": "ok",
+            },
+        )
+
+        # Update: `issue_tracker`.
+        data = {"issue_tracker": False}
+        output = self.app.post(
+            "/api/0/test/options/update", headers=headers, data=data
+        )
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertEqual(
+            data,
+            {
+                "message": "Edited successfully settings of repo: test",
+                "status": "ok",
+            },
+        )
+
+        # check after
+        headers = {"Authorization": "token aaabbbcccddd"}
+        output = self.app.get("/api/0/test/options", headers=headers)
+        self.assertEqual(output.status_code, 200)
+        after = json.loads(output.get_data(as_text=True))
+        self.assertNotEqual(before, after)
+        before["settings"]["issue_tracker"] = False
+        self.assertEqual(after, before)
+
+    def test_api_modify_project_options_json(self):
+        """Test accessing api_modify_project_options w/ auth header and
+        input submitted as JSON instead of HTML arguments."""
+
+        # check before
+        headers = {"Authorization": "token aaabbbcccddd"}
+        output = self.app.get("/api/0/test/options", headers=headers)
+        self.assertEqual(output.status_code, 200)
+        before = json.loads(output.get_data(as_text=True))
+        self.assertEqual(
+            before,
+            {
+                "settings": {
+                    "Enforce_signed-off_commits_in_pull-request": False,
+                    "Minimum_score_to_merge_pull-request": -1,
+                    "Only_assignee_can_merge_pull-request": False,
+                    "Web-hooks": None,
+                    "always_merge": False,
+                    "disable_non_fast-forward_merges": False,
+                    "fedmsg_notifications": True,
+                    "issue_tracker": True,
+                    "issue_tracker_read_only": False,
+                    "issues_default_to_private": False,
+                    "mqtt_notifications": True,
+                    "notify_on_commit_flag": False,
+                    "notify_on_pull-request_flag": False,
+                    "open_metadata_access_to_all": False,
+                    "project_documentation": False,
+                    "pull_request_access_only": False,
+                    "pull_requests": True,
+                    "stomp_notifications": True,
+                },
+                "status": "ok",
+            },
+        )
+
+        # Update: `issue_tracker`.
+        data = json.dumps({"issue_tracker": False})
+        headers["Content-Type"] = "application/json"
+        output = self.app.post(
+            "/api/0/test/options/update", headers=headers, data=data
+        )
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertEqual(
+            data,
+            {
+                "message": "Edited successfully settings of repo: test",
+                "status": "ok",
+            },
+        )
+
+        # check after
+        headers = {"Authorization": "token aaabbbcccddd"}
+        output = self.app.get("/api/0/test/options", headers=headers)
+        self.assertEqual(output.status_code, 200)
+        after = json.loads(output.get_data(as_text=True))
+        self.assertNotEqual(before, after)
+        before["settings"]["issue_tracker"] = False
+        self.assertEqual(after, before)
+
 
 class PagureFlaskApiProjectCreateAPITokenTests(tests.Modeltests):
-    """ Tests for the flask API of pagure for creating user project API token
-    """
+    """Tests for the flask API of pagure for creating user project API token"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiProjectCreateAPITokenTests, self).setUp()
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
         tests.create_tokens_acl(self.session, "aaabbbcccddd", "modify_project")
 
     def test_api_createapitoken_as_owner(self):
-        """ Test accessing api_project_create_token as owner. """
+        """Test accessing api_project_create_token as owner."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         project = pagure.lib.query._get_project(self.session, "test")
@@ -3930,7 +4586,7 @@ class PagureFlaskApiProjectCreateAPITokenTests(tests.Modeltests):
         self.assertEqual(output.status_code, 400)
 
     def test_api_createapitoken_as_admin(self):
-        """ Test accessing api_project_create_token as admin. """
+        """Test accessing api_project_create_token as admin."""
 
         project = pagure.lib.query._get_project(self.session, "test")
 
@@ -3976,7 +4632,7 @@ class PagureFlaskApiProjectCreateAPITokenTests(tests.Modeltests):
         )
 
     def test_api_createapitoken_as_unauthorized(self):
-        """ Test accessing api_project_create_token as project admin
+        """Test accessing api_project_create_token as project admin
         but with unauthorized token ACL.
         """
 
@@ -4020,7 +4676,7 @@ class PagureFlaskApiProjectCreateAPITokenTests(tests.Modeltests):
         self.assertEqual(output.status_code, 401)
 
     def test_api_createapitoken_as_unauthorized_2(self):
-        """ Test accessing api_project_create_token as project user
+        """Test accessing api_project_create_token as project user
         with unauthorized token ACL.
         """
 
@@ -4065,20 +4721,19 @@ class PagureFlaskApiProjectCreateAPITokenTests(tests.Modeltests):
 
 
 class PagureFlaskApiProjectConnectorTests(tests.Modeltests):
-    """ Tests for the flask API of pagure for getting connector of a project
-    """
+    """Tests for the flask API of pagure for getting connector of a project"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiProjectConnectorTests, self).setUp()
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
         tests.create_tokens_acl(self.session, "aaabbbcccddd", "modify_project")
 
     def test_api_get_project_connector_as_owner(self):
-        """ Test accessing api_get_project_connector as project owner. """
+        """Test accessing api_get_project_connector as project owner."""
 
         project = pagure.lib.query._get_project(self.session, "test")
 
@@ -4120,7 +4775,7 @@ class PagureFlaskApiProjectConnectorTests(tests.Modeltests):
         )
 
     def test_api_get_project_connector_as_admin(self):
-        """ Test accessing api_get_project_connector as project admin """
+        """Test accessing api_get_project_connector as project admin"""
 
         project = pagure.lib.query._get_project(self.session, "test")
 
@@ -4185,7 +4840,7 @@ class PagureFlaskApiProjectConnectorTests(tests.Modeltests):
         )
 
     def test_api_get_project_connector_as_unauthorized(self):
-        """ Test accessing api_get_project_connector as project admin
+        """Test accessing api_get_project_connector as project admin
         but with unauthorized token ACL
         """
 
@@ -4220,7 +4875,7 @@ class PagureFlaskApiProjectConnectorTests(tests.Modeltests):
         self.assertEqual(output.status_code, 401)
 
     def test_api_get_project_connector_as_unauthorized_2(self):
-        """ Test accessing api_get_project_connector as project
+        """Test accessing api_get_project_connector as project
         but with unauthorized token ACL
         """
 
@@ -4256,13 +4911,12 @@ class PagureFlaskApiProjectConnectorTests(tests.Modeltests):
 
 
 class PagureFlaskApiProjectWebhookTokenTests(tests.Modeltests):
-    """ Tests for the flask API of pagure for getting webhook token of a project
-    """
+    """Tests for the flask API of pagure for getting webhook token of a project"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiProjectWebhookTokenTests, self).setUp()
         tests.create_projects(self.session)
         tests.create_tokens(self.session, project_id=None)
@@ -4270,7 +4924,7 @@ class PagureFlaskApiProjectWebhookTokenTests(tests.Modeltests):
         tests.create_tokens_acl(self.session, "aaabbbcccddd", "issue_assign")
 
     def test_api_get_project_webhook_token_as_owner(self):
-        """ Test accessing webhook token as project owner. """
+        """Test accessing webhook token as project owner."""
 
         project = pagure.lib.query._get_project(self.session, "test")
 
@@ -4284,7 +4938,7 @@ class PagureFlaskApiProjectWebhookTokenTests(tests.Modeltests):
         )
 
     def test_api_get_project_webhook_token_as_collaborator(self):
-        """ Test accessing webhook token as project collaborator. """
+        """Test accessing webhook token as project collaborator."""
 
         project = pagure.lib.query._get_project(self.session, "test")
 
@@ -4294,7 +4948,7 @@ class PagureFlaskApiProjectWebhookTokenTests(tests.Modeltests):
             project,
             new_user="foo",
             user="pingou",
-            access="ticket",
+            access="collaborator",
         )
         self.session.commit()
 
@@ -4317,7 +4971,7 @@ class PagureFlaskApiProjectWebhookTokenTests(tests.Modeltests):
         )
 
     def test_api_get_project_webhook_token_as_not_collaborator(self):
-        """ Test accessing webhook token as not a project collaborator. """
+        """Test accessing webhook token as not a project collaborator."""
 
         # Create token for foo user with a default ACL
         mtoken = pagure.lib.query.add_token_to_user(
@@ -4335,11 +4989,10 @@ class PagureFlaskApiProjectWebhookTokenTests(tests.Modeltests):
 
 
 class PagureFlaskApiProjectCommitInfotests(tests.Modeltests):
-    """ Tests for the flask API of pagure for commit info
-    """
+    """Tests for the flask API of pagure for commit info"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiProjectCommitInfotests, self).setUp()
 
         tests.create_projects(self.session)
@@ -4352,7 +5005,7 @@ class PagureFlaskApiProjectCommitInfotests(tests.Modeltests):
         self.commit = repo_obj.revparse_single("HEAD")
 
     def test_api_commit_info(self):
-        """ Test flagging a commit with missing precentage. """
+        """Test flagging a commit with missing precentage."""
 
         output = self.app.get("/api/0/test/c/%s/info" % self.commit.oid.hex)
         self.assertEqual(output.status_code, 200)
@@ -4371,7 +5024,7 @@ class PagureFlaskApiProjectCommitInfotests(tests.Modeltests):
         self.assertEqual(data, expected_output)
 
     def test_api_commit_info_invalid_commit(self):
-        """ Test flagging a commit with missing username. """
+        """Test flagging a commit with missing username."""
         output = self.app.get("/api/0/test/c/invalid_commit_hash/info")
 
         self.assertEqual(output.status_code, 404)
@@ -4382,7 +5035,7 @@ class PagureFlaskApiProjectCommitInfotests(tests.Modeltests):
         self.assertEqual(pagure.api.APIERROR.ENOCOMMIT.value, data["error"])
 
     def test_api_commit_info_hash_tree(self):
-        """ Test flagging a commit with missing username. """
+        """Test flagging a commit with missing username."""
         output = self.app.get(
             "/api/0/test/c/%s/info" % self.commit.tree_id.hex
         )
@@ -4396,13 +5049,12 @@ class PagureFlaskApiProjectCommitInfotests(tests.Modeltests):
 
 
 class PagureFlaskApiProjectGitBranchestests(tests.Modeltests):
-    """ Tests for the flask API of pagure for git branches
-    """
+    """Tests for the flask API of pagure for git branches"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiProjectGitBranchestests, self).setUp()
 
         tests.create_projects(self.session)
@@ -4430,7 +5082,7 @@ class PagureFlaskApiProjectGitBranchestests(tests.Modeltests):
             PagureRepo.push(clone_repo.remotes[0], refname)
 
     def test_api_git_branches(self):
-        """ Test the api_git_branches method of the flask api. """
+        """Test the api_git_branches method of the flask api."""
         # Check that the branches show up on the API
         output = self.app.get("/api/0/test/git/branches")
 
@@ -4446,7 +5098,7 @@ class PagureFlaskApiProjectGitBranchestests(tests.Modeltests):
         )
 
     def test_api_git_branches_with_commits(self):
-        """ Test the api_git_branches method of the flask api with with_commits=True. """
+        """Test the api_git_branches method of the flask api with with_commits=True."""
         # Check that the branches show up on the API
         output = self.app.get("/api/0/test/git/branches?with_commits=true")
 
@@ -4460,13 +5112,15 @@ class PagureFlaskApiProjectGitBranchestests(tests.Modeltests):
                     "pats-win-49": self.commit.hex,
                     "pats-win-51": self.commit.hex,
                 },
-                "default": {"master": self.commit.hex,},
+                "default": {
+                    "master": self.commit.hex,
+                },
                 "total_branches": 3,
             },
         )
 
     def test_api_git_branches_empty_repo(self):
-        """ Test the api_git_branches method of the flask api when the repo is
+        """Test the api_git_branches method of the flask api when the repo is
         empty.
         """
         # Check that no branches show up on the API
@@ -4478,7 +5132,7 @@ class PagureFlaskApiProjectGitBranchestests(tests.Modeltests):
         )
 
     def test_api_set_git_default_branch(self):
-        """ Test the api_git_branches method of the flask api. """
+        """Test the api_git_branches method of the flask api."""
         headers = {"Authorization": "token foo_token"}
         data = {"branch_name": "pats-win-49"}
         output = self.app.post(
@@ -4496,7 +5150,7 @@ class PagureFlaskApiProjectGitBranchestests(tests.Modeltests):
         )
 
     def test_api_set_git_default_branch_with_commits_form(self):
-        """ Test the api_git_branches method of the flask api with with_commits=True. """
+        """Test the api_git_branches method of the flask api with with_commits=True."""
         headers = {"Authorization": "token foo_token"}
         data = {"branch_name": "pats-win-49", "with_commits": True}
         output = self.app.post(
@@ -4512,13 +5166,15 @@ class PagureFlaskApiProjectGitBranchestests(tests.Modeltests):
                     "pats-win-49": self.commit.hex,
                     "pats-win-51": self.commit.hex,
                 },
-                "default": {"pats-win-49": self.commit.hex,},
+                "default": {
+                    "pats-win-49": self.commit.hex,
+                },
                 "total_branches": 3,
             },
         )
 
     def test_api_set_git_default_branch_with_commits_url(self):
-        """ Test the api_git_branches method of the flask api with with_commits=True. """
+        """Test the api_git_branches method of the flask api with with_commits=True."""
         headers = {"Authorization": "token foo_token"}
         data = {"branch_name": "pats-win-49"}
         output = self.app.post(
@@ -4536,13 +5192,15 @@ class PagureFlaskApiProjectGitBranchestests(tests.Modeltests):
                     "pats-win-49": self.commit.hex,
                     "pats-win-51": self.commit.hex,
                 },
-                "default": {"pats-win-49": self.commit.hex,},
+                "default": {
+                    "pats-win-49": self.commit.hex,
+                },
                 "total_branches": 3,
             },
         )
 
     def test_api_set_git_default_branch_invalid_branch(self):
-        """ Test the api_git_branches method of the flask api with with_commits=True. """
+        """Test the api_git_branches method of the flask api with with_commits=True."""
         headers = {"Authorization": "token foo_token"}
         data = {"branch_name": "main"}
         output = self.app.post(
@@ -4561,11 +5219,13 @@ class PagureFlaskApiProjectGitBranchestests(tests.Modeltests):
         )
 
     def test_api_set_git_default_branch_invalid_token(self):
-        """ Test the api_git_branches method of the flask api with with_commits=True. """
+        """Test the api_git_branches method of the flask api with with_commits=True."""
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {"branch_name": "main"}
         output = self.app.post(
-            "/api/0/test/git/branches", data=data, headers=headers,
+            "/api/0/test/git/branches",
+            data=data,
+            headers=headers,
         )
         self.assertEqual(output.status_code, 401)
         data = json.loads(output.get_data(as_text=True))
@@ -4581,7 +5241,7 @@ class PagureFlaskApiProjectGitBranchestests(tests.Modeltests):
         )
 
     def test_api_set_git_default_branch_empty_repo(self):
-        """ Test the api_git_branches method of the flask api when the repo is
+        """Test the api_git_branches method of the flask api when the repo is
         empty.
         """
         headers = {"Authorization": "token foo_token"}
@@ -4601,13 +5261,12 @@ class PagureFlaskApiProjectGitBranchestests(tests.Modeltests):
 
 
 class PagureFlaskApiProjectCreateProjectTests(tests.Modeltests):
-    """ Tests for the flask API of pagure for git branches
-    """
+    """Tests for the flask API of pagure for git branches"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiProjectCreateProjectTests, self).setUp()
 
         tests.create_projects(self.session)
@@ -4770,6 +5429,9 @@ class PagureFlaskApiProjectCreateProjectTests(tests.Modeltests):
         data = json.loads(output.get_data(as_text=True))
         self.assertDictEqual(data, {"message": 'Project "api1" created'})
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     def test_api_new_project(self):
 
         headers = {"Authorization": "token aaabbbcccddd"}
@@ -4779,7 +5441,51 @@ class PagureFlaskApiProjectCreateProjectTests(tests.Modeltests):
         }
 
         # Valid request
-        output = self.app.post("/api/0/new/", data=data, headers=headers)
+        with testing.mock_sends(
+            pagure_messages.ProjectNewV1(
+                topic="pagure.project.new",
+                body={
+                    "project": {
+                        "id": 4,
+                        "name": "test_42",
+                        "fullname": "test_42",
+                        "url_path": "test_42",
+                        "description": "Just another small test project",
+                        "full_url": "http://localhost.localdomain/test_42",
+                        "namespace": None,
+                        "parent": None,
+                        "date_created": ANY,
+                        "date_modified": ANY,
+                        "user": {
+                            "name": "pingou",
+                            "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                            "url_path": "user/pingou",
+                        },
+                        "access_users": {
+                            "owner": ["pingou"],
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "access_groups": {
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "tags": [],
+                        "priorities": {},
+                        "custom_keys": [],
+                        "close_status": [],
+                        "milestones": {},
+                    },
+                    "agent": "pingou",
+                },
+            )
+        ):
+            output = self.app.post("/api/0/new/", data=data, headers=headers)
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
         self.assertDictEqual(data, {"message": 'Project "test_42" created'})
@@ -4851,8 +5557,8 @@ class PagureFlaskApiProjectCreateProjectTests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_api_new_project_private(self):
-        """ Test the api_new_project method of the flask api to create
-        a private project. """
+        """Test the api_new_project method of the flask api to create
+        a private project."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -4871,7 +5577,7 @@ class PagureFlaskApiProjectCreateProjectTests(tests.Modeltests):
         )
 
     def test_api_new_project_user_token(self):
-        """ Test the api_new_project method of the flask api. """
+        """Test the api_new_project method of the flask api."""
 
         headers = {"Authorization": "token foo_token_user"}
 
@@ -4983,7 +5689,7 @@ class PagureFlaskApiProjectCreateProjectTests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"USER_NAMESPACE": True})
     def test_api_new_project_user_ns(self):
-        """ Test the api_new_project method of the flask api. """
+        """Test the api_new_project method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
diff --git a/tests/test_pagure_flask_api_project_blockuser.py b/tests/test_pagure_flask_api_project_blockuser.py
index 10ecb91..004e07f 100644
--- a/tests/test_pagure_flask_api_project_blockuser.py
+++ b/tests/test_pagure_flask_api_project_blockuser.py
@@ -34,14 +34,14 @@ import tests
 
 
 class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest):
-    """ Tests for the flask API of pagure for assigning a PR """
+    """Tests for the flask API of pagure for assigning a PR"""
 
     maxDiff = None
 
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiProjectBlockuserTests, self).setUp()
 
         tests.create_projects(self.session)
@@ -72,15 +72,14 @@ class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest):
         self.session.commit()
 
     def tearDown(self):
-        """ Tears down the environment at the end of the tests. """
+        """Tears down the environment at the end of the tests."""
         project = pagure.lib.query.get_authorized_project(self.session, "test")
         self.assertEqual(project.block_users, self.blocked_users)
 
         super(PagureFlaskApiProjectBlockuserTests, self).tearDown()
 
     def test_api_blockuser_no_token(self):
-        """ Test api_project_block_user method when no token is provided.
-        """
+        """Test api_project_block_user method when no token is provided."""
 
         # No token
         output = self.app.post("/api/0/test/blockuser")
@@ -98,8 +97,7 @@ class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest):
         )
 
     def test_api_blockuser_invalid_token(self):
-        """ Test api_project_block_user method when the token provided is invalid.
-        """
+        """Test api_project_block_user method when the token provided is invalid."""
 
         headers = {"Authorization": "token aaabbbcccd"}
 
@@ -119,8 +117,7 @@ class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest):
         )
 
     def test_api_blockuser_no_data(self):
-        """ Test api_project_block_user method when no data is provided.
-        """
+        """Test api_project_block_user method when no data is provided."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -131,7 +128,7 @@ class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest):
         self.assertDictEqual(data, {"message": "User(s) blocked"})
 
     def test_api_blockuser_invalid_user(self):
-        """ Test api_project_block_user method when the data provided includes
+        """Test api_project_block_user method when the data provided includes
         an invalid username.
         """
 
@@ -149,7 +146,7 @@ class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest):
         )
 
     def test_api_blockuser_insufficient_rights(self):
-        """ Test api_project_block_user method when the user doing the action
+        """Test api_project_block_user method when the user doing the action
         does not have admin priviledges.
         """
 
@@ -172,8 +169,7 @@ class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest):
         )
 
     def test_api_blockuser_with_data(self):
-        """ Test api_pull_request_assign method when the project doesn't exist.
-        """
+        """Test api_pull_request_assign method when the project doesn't exist."""
         self.blocked_users = ["foo"]
 
         headers = {"Authorization": "token aaabbbcccddd"}
@@ -199,8 +195,7 @@ class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest):
         self.assertDictEqual(data, {"message": "User(s) blocked"})
 
     def test_api_blockeduser_api(self):
-        """ Test doing a POST request to the API when the user is blocked.
-        """
+        """Test doing a POST request to the API when the user is blocked."""
         self.blocked_users = ["pingou"]
 
         headers = {"Authorization": "token aaabbbcccddd"}
@@ -232,8 +227,7 @@ class PagureFlaskApiProjectBlockuserTests(tests.SimplePagureTest):
         )
 
     def test_ui_new_issue_user_blocked(self):
-        """ Test doing a POST request to the UI when the user is blocked.
-        """
+        """Test doing a POST request to the UI when the user is blocked."""
 
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
diff --git a/tests/test_pagure_flask_api_project_contributors.py b/tests/test_pagure_flask_api_project_contributors.py
new file mode 100644
index 0000000..2090cc9
--- /dev/null
+++ b/tests/test_pagure_flask_api_project_contributors.py
@@ -0,0 +1,722 @@
+# -*- coding: utf-8 -*-
+
+"""
+ (c) 2020 - Copyright Red Hat Inc
+
+ Authors:
+   Pierre-Yves Chibon <pingou@pingoured.fr>
+
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+import arrow
+import copy
+import datetime
+import unittest
+import shutil
+import sys
+import time
+import os
+
+import flask
+import json
+import munch
+from mock import patch, MagicMock
+from sqlalchemy.exc import SQLAlchemyError
+
+sys.path.insert(
+    0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
+)
+
+import pagure.lib.query
+import tests
+
+
+class PagureFlaskApiProjectContributorsTests(tests.SimplePagureTest):
+    """Tests for the flask API of pagure for listing contributors of a project"""
+
+    maxDiff = None
+
+    @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
+    @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
+    def setUp(self):
+        """Set up the environnment, ran before every tests."""
+        super(PagureFlaskApiProjectContributorsTests, self).setUp()
+
+        tests.create_projects(self.session)
+        tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
+
+    def test_private_project(self):
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        project.private = True
+        self.session.add(project)
+        self.session.commit()
+
+        output = self.app.get("/api/0/test/contributors")
+        self.assertEqual(output.status_code, 404)
+        expected_rv = {
+            "error": "Project not found",
+            "error_code": "ENOPROJECT",
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_just_main_admin(self):
+
+        output = self.app.get("/api/0/test/contributors")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "groups": {
+                "admin": [],
+                "collaborators": [],
+                "commit": [],
+                "ticket": [],
+            },
+            "users": {
+                "admin": ["pingou"],
+                "collaborators": [],
+                "commit": [],
+                "ticket": [],
+            },
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_admin_user(self):
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="foo",
+            user="pingou",
+            access="admin",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        output = self.app.get("/api/0/test/contributors")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "groups": {
+                "admin": [],
+                "collaborators": [],
+                "commit": [],
+                "ticket": [],
+            },
+            "users": {
+                "admin": ["foo", "pingou"],
+                "collaborators": [],
+                "commit": [],
+                "ticket": [],
+            },
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_admin_user_and_commit(self):
+
+        tests.create_user(self.session, "baz", "foo baz", ["baz@bar.com"])
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="foo",
+            user="pingou",
+            access="admin",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="baz",
+            user="pingou",
+            access="commit",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        output = self.app.get("/api/0/test/contributors")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "groups": {
+                "admin": [],
+                "collaborators": [],
+                "commit": [],
+                "ticket": [],
+            },
+            "users": {
+                "admin": ["foo", "pingou"],
+                "collaborators": [],
+                "commit": ["baz"],
+                "ticket": [],
+            },
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_admin_user_and_commit_and_ticket(self):
+
+        tests.create_user(self.session, "baz", "foo baz", ["baz@bar.com"])
+        tests.create_user(
+            self.session, "alex", "Alex Ander", ["alex@ander.com"]
+        )
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="foo",
+            user="pingou",
+            access="admin",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="baz",
+            user="pingou",
+            access="commit",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="alex",
+            user="pingou",
+            access="ticket",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        output = self.app.get("/api/0/test/contributors")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "groups": {
+                "admin": [],
+                "collaborators": [],
+                "commit": [],
+                "ticket": [],
+            },
+            "users": {
+                "admin": ["foo", "pingou"],
+                "collaborators": [],
+                "commit": ["baz"],
+                "ticket": ["alex"],
+            },
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_admin_user_and_commit_and_ticket_and_contributors(self):
+
+        tests.create_user(self.session, "baz", "foo baz", ["baz@bar.com"])
+        tests.create_user(
+            self.session, "alex", "Alex Ander", ["alex@ander.com"]
+        )
+        tests.create_user(self.session, "ralph", "Ralph B.", ["ralph@b.com"])
+        tests.create_user(self.session, "kevin", "Kevin F.", ["kevin@f.com"])
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="foo",
+            user="pingou",
+            access="admin",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="baz",
+            user="pingou",
+            access="commit",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="alex",
+            user="pingou",
+            access="ticket",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="ralph",
+            user="pingou",
+            access="collaborator",
+            branches="epel*",
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="kevin",
+            user="pingou",
+            access="collaborator",
+            branches="f*",
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        output = self.app.get("/api/0/test/contributors")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "groups": {
+                "admin": [],
+                "collaborators": [],
+                "commit": [],
+                "ticket": [],
+            },
+            "users": {
+                "admin": ["foo", "pingou"],
+                "collaborators": [
+                    {"branches": "f*", "user": "kevin"},
+                    {"branches": "epel*", "user": "ralph"},
+                ],
+                "commit": ["baz"],
+                "ticket": ["alex"],
+            },
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_admin_group(self):
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="admin_groups",
+            user="pingou",
+            access="admin",
+            branches=None,
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        output = self.app.get("/api/0/test/contributors")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "groups": {
+                "admin": ["admin_groups"],
+                "collaborators": [],
+                "commit": [],
+                "ticket": [],
+            },
+            "users": {
+                "admin": ["pingou"],
+                "collaborators": [],
+                "commit": [],
+                "ticket": [],
+            },
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_admin_and_commit_groups(self):
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="admin_groups",
+            user="pingou",
+            access="admin",
+            branches=None,
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="commit_group",
+            user="pingou",
+            access="commit",
+            branches=None,
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        output = self.app.get("/api/0/test/contributors")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "groups": {
+                "admin": ["admin_groups"],
+                "collaborators": [],
+                "commit": ["commit_group"],
+                "ticket": [],
+            },
+            "users": {
+                "admin": ["pingou"],
+                "collaborators": [],
+                "commit": [],
+                "ticket": [],
+            },
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_admin_and_commit_and_ticket_groups(self):
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="admin_groups",
+            user="pingou",
+            access="admin",
+            branches=None,
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="commit_group",
+            user="pingou",
+            access="commit",
+            branches=None,
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="ticket_group",
+            user="pingou",
+            access="ticket",
+            branches=None,
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        output = self.app.get("/api/0/test/contributors")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "groups": {
+                "admin": ["admin_groups"],
+                "collaborators": [],
+                "commit": ["commit_group"],
+                "ticket": ["ticket_group"],
+            },
+            "users": {
+                "admin": ["pingou"],
+                "collaborators": [],
+                "commit": [],
+                "ticket": [],
+            },
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_admin_and_commit_and_ticket_and_collaborators_groups(self):
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="admin_groups",
+            user="pingou",
+            access="admin",
+            branches=None,
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="commit_group",
+            user="pingou",
+            access="commit",
+            branches=None,
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="ticket_group",
+            user="pingou",
+            access="ticket",
+            branches=None,
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="epel_group",
+            user="pingou",
+            access="collaborator",
+            branches="epel*",
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="fedora_group",
+            user="pingou",
+            access="collaborator",
+            branches="f*",
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        output = self.app.get("/api/0/test/contributors")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "groups": {
+                "admin": ["admin_groups"],
+                "collaborators": [
+                    {"branches": "epel*", "user": "epel_group"},
+                    {"branches": "f*", "user": "fedora_group"},
+                ],
+                "commit": ["commit_group"],
+                "ticket": ["ticket_group"],
+            },
+            "users": {
+                "admin": ["pingou"],
+                "collaborators": [],
+                "commit": [],
+                "ticket": [],
+            },
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_full(self):
+
+        tests.create_user(self.session, "baz", "foo baz", ["baz@bar.com"])
+        tests.create_user(
+            self.session, "alex", "Alex Ander", ["alex@ander.com"]
+        )
+        tests.create_user(self.session, "ralph", "Ralph B.", ["ralph@b.com"])
+        tests.create_user(self.session, "kevin", "Kevin F.", ["kevin@f.com"])
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+
+        # Add users for all kinds of access
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="foo",
+            user="pingou",
+            access="admin",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="baz",
+            user="pingou",
+            access="commit",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="alex",
+            user="pingou",
+            access="ticket",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="ralph",
+            user="pingou",
+            access="collaborator",
+            branches="epel*",
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="kevin",
+            user="pingou",
+            access="collaborator",
+            branches="f*",
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        # Create groups for all kinds of access
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="admin_groups",
+            user="pingou",
+            access="admin",
+            branches=None,
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="commit_group",
+            user="pingou",
+            access="commit",
+            branches=None,
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="ticket_group",
+            user="pingou",
+            access="ticket",
+            branches=None,
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="epel_group",
+            user="pingou",
+            access="collaborator",
+            branches="epel*",
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        msg = pagure.lib.query.add_group_to_project(
+            session=self.session,
+            project=project,
+            new_group="fedora_group",
+            user="pingou",
+            access="collaborator",
+            branches="f*",
+            create=True,
+            is_admin=False,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "Group added")
+
+        output = self.app.get("/api/0/test/contributors")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "groups": {
+                "admin": ["admin_groups"],
+                "collaborators": [
+                    {"branches": "epel*", "user": "epel_group"},
+                    {"branches": "f*", "user": "fedora_group"},
+                ],
+                "commit": ["commit_group"],
+                "ticket": ["ticket_group"],
+            },
+            "users": {
+                "admin": ["foo", "pingou"],
+                "collaborators": [
+                    {"branches": "f*", "user": "kevin"},
+                    {"branches": "epel*", "user": "ralph"},
+                ],
+                "commit": ["baz"],
+                "ticket": ["alex"],
+            },
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+
+if __name__ == "__main__":
+    unittest.main(verbosity=2)
diff --git a/tests/test_pagure_flask_api_project_delete_project.py b/tests/test_pagure_flask_api_project_delete_project.py
index 1a0cb7c..0831252 100644
--- a/tests/test_pagure_flask_api_project_delete_project.py
+++ b/tests/test_pagure_flask_api_project_delete_project.py
@@ -34,7 +34,7 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureFlaskApiProjectDeleteProjecttests(tests.Modeltests):
-    """ Tests for the flask API of pagure for deleting projects """
+    """Tests for the flask API of pagure for deleting projects"""
 
     maxDiff = None
 
@@ -175,6 +175,7 @@ class PagureFlaskApiProjectDeleteProjecttests(tests.Modeltests):
                     "date_created": "1595341690",
                     "date_modified": "1595341690",
                     "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
                     "fullname": "test",
                     "id": 1,
                     "milestones": {},
@@ -188,6 +189,7 @@ class PagureFlaskApiProjectDeleteProjecttests(tests.Modeltests):
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                 },
             },
diff --git a/tests/test_pagure_flask_api_project_git_alias.py b/tests/test_pagure_flask_api_project_git_alias.py
new file mode 100644
index 0000000..43f1b3e
--- /dev/null
+++ b/tests/test_pagure_flask_api_project_git_alias.py
@@ -0,0 +1,288 @@
+# -*- coding: utf-8 -*-
+
+"""
+ (c) 2020 - Copyright Red Hat Inc
+
+ Authors:
+   Pierre-Yves Chibon <pingou@pingoured.fr>
+
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+import unittest
+import shutil
+import sys
+import os
+
+import json
+import pygit2
+from mock import patch, MagicMock
+
+sys.path.insert(
+    0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
+)
+
+import pagure.api
+import pagure.flask_app
+import pagure.lib.query
+import tests
+
+
+def set_projects_up(self):
+    tests.create_projects(self.session)
+    tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
+    tests.add_content_git_repo(os.path.join(self.path, "repos", "test.git"))
+    tests.create_tokens(self.session)
+    tests.create_tokens_acl(self.session)
+
+    self.session.commit()
+
+
+def set_up_board(self):
+    headers = {
+        "Authorization": "token aaabbbcccddd",
+        "Content-Type": "application/json",
+    }
+
+    data = json.dumps({"dev": {"active": True, "tag": "dev"}})
+    output = self.app.post("/api/0/test/boards", headers=headers, data=data)
+    self.assertEqual(output.status_code, 200)
+    data = json.loads(output.get_data(as_text=True))
+    self.assertDictEqual(
+        data,
+        {
+            "boards": [
+                {
+                    "active": True,
+                    "full_url": "http://localhost.localdomain/test/boards/dev",
+                    "name": "dev",
+                    "status": [],
+                    "tag": {
+                        "tag": "dev",
+                        "tag_color": "DeepBlueSky",
+                        "tag_description": "",
+                    },
+                }
+            ]
+        },
+    )
+
+
+class PagureFlaskApiProjectGitAliastests(tests.SimplePagureTest):
+    """Tests for flask API for branch alias in pagure"""
+
+    maxDiff = None
+
+    def setUp(self):
+        super(PagureFlaskApiProjectGitAliastests, self).setUp()
+
+        set_projects_up(self)
+        self.repo_obj = pygit2.Repository(
+            os.path.join(self.path, "repos", "test.git")
+        )
+
+    def test_api_git_alias_view_no_project(self):
+        output = self.app.get("/api/0/invalid/git/alias")
+        self.assertEqual(output.status_code, 404)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data, {"error": "Project not found", "error_code": "ENOPROJECT"}
+        )
+
+    def test_api_git_alias_view_empty(self):
+        output = self.app.get("/api/0/test/git/alias")
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, {})
+
+    def test_api_new_git_alias_no_data(self):
+        data = "{}"
+        headers = {
+            "Authorization": "token aaabbbcccddd",
+            "Content-Type": "application/json",
+        }
+        output = self.app.post(
+            "/api/0/test/git/alias/new", headers=headers, data=data
+        )
+
+        self.assertEqual(output.status_code, 400)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data,
+            {
+                "error": "Invalid or incomplete input submitted",
+                "error_code": "EINVALIDREQ",
+            },
+        )
+
+    def test_api_new_git_alias_invalid_data(self):
+        data = json.dumps({"dev": "foobar"})
+        headers = {
+            "Authorization": "token aaabbbcccddd",
+            "Content-Type": "application/json",
+        }
+        output = self.app.post(
+            "/api/0/test/git/alias/new", headers=headers, data=data
+        )
+
+        self.assertEqual(output.status_code, 400)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data,
+            {
+                "error": "Invalid or incomplete input submitted",
+                "error_code": "EINVALIDREQ",
+            },
+        )
+
+    def test_api_new_git_alias_missing_data(self):
+        data = json.dumps({"alias_from": "mster"})
+        headers = {
+            "Authorization": "token aaabbbcccddd",
+            "Content-Type": "application/json",
+        }
+        output = self.app.post(
+            "/api/0/test/git/alias/new", headers=headers, data=data
+        )
+
+        self.assertEqual(output.status_code, 400)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data,
+            {
+                "error": "Invalid or incomplete input submitted",
+                "error_code": "EINVALIDREQ",
+            },
+        )
+
+    def test_api_new_git_alias_no_existant_branch(self):
+        data = json.dumps({"alias_from": "master", "alias_to": "main"})
+        headers = {
+            "Authorization": "token aaabbbcccddd",
+            "Content-Type": "application/json",
+        }
+        output = self.app.post(
+            "/api/0/test/git/alias/new", headers=headers, data=data
+        )
+
+        self.assertEqual(output.status_code, 400)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data,
+            {
+                "error": "Branch not found in this git repository",
+                "error_code": "EBRANCHNOTFOUND",
+            },
+        )
+
+    def test_api_new_git_alias(self):
+        data = json.dumps({"alias_from": "main", "alias_to": "master"})
+        headers = {
+            "Authorization": "token aaabbbcccddd",
+            "Content-Type": "application/json",
+        }
+        output = self.app.post(
+            "/api/0/test/git/alias/new", headers=headers, data=data
+        )
+
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, {"refs/heads/main": "refs/heads/master"})
+
+    def test_api_drop_git_alias_no_data(self):
+        data = "{}"
+        headers = {
+            "Authorization": "token aaabbbcccddd",
+            "Content-Type": "application/json",
+        }
+        output = self.app.post(
+            "/api/0/test/git/alias/drop", headers=headers, data=data
+        )
+
+        self.assertEqual(output.status_code, 400)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data,
+            {
+                "error": "Invalid or incomplete input submitted",
+                "error_code": "EINVALIDREQ",
+            },
+        )
+
+    def test_api_drop_git_alias_invalid_data(self):
+        data = json.dumps({"dev": "foobar"})
+        headers = {
+            "Authorization": "token aaabbbcccddd",
+            "Content-Type": "application/json",
+        }
+        output = self.app.post(
+            "/api/0/test/git/alias/drop", headers=headers, data=data
+        )
+
+        self.assertEqual(output.status_code, 400)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data,
+            {
+                "error": "Invalid or incomplete input submitted",
+                "error_code": "EINVALIDREQ",
+            },
+        )
+
+    def test_api_drop_git_alias_missing_data(self):
+        data = json.dumps({"alias_from": "mster"})
+        headers = {
+            "Authorization": "token aaabbbcccddd",
+            "Content-Type": "application/json",
+        }
+        output = self.app.post(
+            "/api/0/test/git/alias/drop", headers=headers, data=data
+        )
+
+        self.assertEqual(output.status_code, 400)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data,
+            {
+                "error": "Invalid or incomplete input submitted",
+                "error_code": "EINVALIDREQ",
+            },
+        )
+
+    def test_api_drop_git_alias_no_existant_branch(self):
+        data = json.dumps({"alias_from": "master", "alias_to": "main"})
+        headers = {
+            "Authorization": "token aaabbbcccddd",
+            "Content-Type": "application/json",
+        }
+        output = self.app.post(
+            "/api/0/test/git/alias/drop", headers=headers, data=data
+        )
+
+        self.assertEqual(output.status_code, 400)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(
+            data,
+            {
+                "error": "Branch not found in this git repository",
+                "error_code": "EBRANCHNOTFOUND",
+            },
+        )
+
+    def test_api_drop_git_alias(self):
+        data = json.dumps({"alias_from": "main", "alias_to": "master"})
+        headers = {
+            "Authorization": "token aaabbbcccddd",
+            "Content-Type": "application/json",
+        }
+        output = self.app.post(
+            "/api/0/test/git/alias/drop", headers=headers, data=data
+        )
+        self.assertEqual(output.status_code, 200)
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, {})
+
+
+if __name__ == "__main__":
+    unittest.main(verbosity=2)
diff --git a/tests/test_pagure_flask_api_project_git_tags.py b/tests/test_pagure_flask_api_project_git_tags.py
index 53c0c68..b7c3a17 100644
--- a/tests/test_pagure_flask_api_project_git_tags.py
+++ b/tests/test_pagure_flask_api_project_git_tags.py
@@ -22,12 +22,12 @@ import pagure.lib.query
 
 
 class PagureFlaskApiProjectGitTagstests(tests.Modeltests):
-    """ Tests for the flask API of pagure for creating new git tags """
+    """Tests for the flask API of pagure for creating new git tags"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiProjectGitTagstests, self).setUp()
 
         tests.create_projects(self.session)
@@ -41,7 +41,7 @@ class PagureFlaskApiProjectGitTagstests(tests.Modeltests):
         self.headers = {"Authorization": "token aaabbbcccddd"}
 
     def test_api_new_git_tags_no_project(self):
-        """ Test the api_new_git_tags function.  """
+        """Test the api_new_git_tags function."""
         output = self.app.post("/api/0/foo/git/tags", headers=self.headers)
         self.assertEqual(output.status_code, 404)
         expected_rv = {
@@ -52,7 +52,7 @@ class PagureFlaskApiProjectGitTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_new_git_tags_invalid_auth(self):
-        """ Test the api_new_git_tags function.  """
+        """Test the api_new_git_tags function."""
         headers = self.headers
         headers["Authorization"] += "foo"
         output = self.app.post("/api/0/foo/git/tags", headers=headers)
@@ -72,7 +72,7 @@ class PagureFlaskApiProjectGitTagstests(tests.Modeltests):
         self.assertEqual("Invalid token", data["errors"])
 
     def test_api_new_git_tag(self):
-        """ Test the api_new_git_tags function.  """
+        """Test the api_new_git_tags function."""
 
         # Before
         output = self.app.get("/api/0/test/git/tags")
@@ -113,7 +113,7 @@ class PagureFlaskApiProjectGitTagstests(tests.Modeltests):
         self.assertEqual(data["total_tags"], 1)
 
     def test_api_new_git_tag_with_commits(self):
-        """ Test the api_new_git_tags function.  """
+        """Test the api_new_git_tags function."""
 
         # Before
         output = self.app.get("/api/0/test/git/tags")
@@ -148,7 +148,7 @@ class PagureFlaskApiProjectGitTagstests(tests.Modeltests):
         self.assertEqual(data["tag_created"], True)
 
     def test_api_new_git_tag_with_message(self):
-        """ Test the api_new_git_tags function.  """
+        """Test the api_new_git_tags function."""
 
         # Before
         output = self.app.get("/api/0/test/git/tags")
@@ -180,7 +180,7 @@ class PagureFlaskApiProjectGitTagstests(tests.Modeltests):
         self.assertEqual(data["tag_created"], True)
 
     def test_api_new_git_tag_with_message_twice(self):
-        """ Test the api_new_git_tags function.  """
+        """Test the api_new_git_tags function."""
 
         # Before
         output = self.app.get("/api/0/test/git/tags")
@@ -231,7 +231,7 @@ class PagureFlaskApiProjectGitTagstests(tests.Modeltests):
         self.assertEqual(data["tag_created"], False)
 
     def test_api_new_git_tag_user_no_access(self):
-        """ Test the api_new_git_tags function.  """
+        """Test the api_new_git_tags function."""
 
         tests.create_tokens(
             self.session, user_id=2, project_id=2, suffix="foo"
@@ -269,7 +269,7 @@ class PagureFlaskApiProjectGitTagstests(tests.Modeltests):
         self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])
 
     def test_api_new_git_tag_user_global_token(self):
-        """ Test the api_new_git_tags function.  """
+        """Test the api_new_git_tags function."""
 
         tests.create_tokens(
             self.session, user_id=2, project_id=None, suffix="foo"
@@ -308,7 +308,7 @@ class PagureFlaskApiProjectGitTagstests(tests.Modeltests):
         self.assertEqual(data["tag_created"], True)
 
     def test_api_new_git_tag_forced(self):
-        """ Test the api_new_git_tags function.  """
+        """Test the api_new_git_tags function."""
 
         # Before
         output = self.app.get("/api/0/test/git/tags")
diff --git a/tests/test_pagure_flask_api_project_hascommit.py b/tests/test_pagure_flask_api_project_hascommit.py
new file mode 100644
index 0000000..d6c99ca
--- /dev/null
+++ b/tests/test_pagure_flask_api_project_hascommit.py
@@ -0,0 +1,563 @@
+# -*- coding: utf-8 -*-
+
+"""
+ (c) 2021 - Copyright Red Hat Inc
+
+ Authors:
+   Pierre-Yves Chibon <pingou@pingoured.fr>
+
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+import arrow
+import copy
+import datetime
+import unittest
+import shutil
+import sys
+import time
+import os
+
+import flask
+import json
+import munch
+from mock import ANY, patch, MagicMock
+from sqlalchemy.exc import SQLAlchemyError
+
+sys.path.insert(
+    0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
+)
+
+import pagure.lib.query
+import tests
+
+
+class PagureFlaskApiProjectHascommitTests(tests.SimplePagureTest):
+    """Tests for the flask API of pagure for listing contributors of a project"""
+
+    maxDiff = None
+
+    @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
+    @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
+    def setUp(self):
+        """Set up the environnment, ran before every tests."""
+        super(PagureFlaskApiProjectHascommitTests, self).setUp()
+
+        tests.create_projects(self.session)
+        tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
+
+    def test_private_project(self):
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        project.private = True
+        self.session.add(project)
+        self.session.commit()
+
+        output = self.app.get("/api/0/test/hascommit?user=pingou")
+        self.assertEqual(output.status_code, 404)
+        expected_rv = {
+            "error": "Project not found",
+            "error_code": "ENOPROJECT",
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_missing_branch(self):
+
+        output = self.app.get("/api/0/test/hascommit?user=pingou")
+        self.assertEqual(output.status_code, 400)
+        expected_rv = {
+            "error": "Invalid or incomplete input submitted",
+            "error_code": "EINVALIDREQ",
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_missing_user(self):
+
+        output = self.app.get("/api/0/test/hascommit?branch=user")
+        self.assertEqual(output.status_code, 400)
+        expected_rv = {
+            "error": "Invalid or incomplete input submitted",
+            "error_code": "EINVALIDREQ",
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_just_main_admin(self):
+
+        output = self.app.get("/api/0/test/hascommit?user=pingou&branch=main")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "args": {
+                "branch": "main",
+                "project": {
+                    "access_groups": {
+                        "admin": [],
+                        "collaborator": [],
+                        "commit": [],
+                        "ticket": [],
+                    },
+                    "access_users": {
+                        "admin": [],
+                        "collaborator": [],
+                        "commit": [],
+                        "owner": ["pingou"],
+                        "ticket": [],
+                    },
+                    "close_status": [
+                        "Invalid",
+                        "Insufficient data",
+                        "Fixed",
+                        "Duplicate",
+                    ],
+                    "custom_keys": [],
+                    "date_created": ANY,
+                    "date_modified": ANY,
+                    "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
+                    "fullname": "test",
+                    "id": 1,
+                    "milestones": {},
+                    "name": "test",
+                    "namespace": None,
+                    "parent": None,
+                    "priorities": {},
+                    "tags": [],
+                    "url_path": "test",
+                    "user": {
+                        "full_url": "http://localhost.localdomain/user/pingou",
+                        "fullname": "PY C",
+                        "name": "pingou",
+                        "url_path": "user/pingou",
+                    },
+                },
+                "user": "pingou",
+            },
+            "hascommit": True,
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_admin_user(self):
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="foo",
+            user="pingou",
+            access="admin",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        output = self.app.get("/api/0/test/hascommit?user=foo&branch=main")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "args": {
+                "branch": "main",
+                "project": {
+                    "access_groups": {
+                        "admin": [],
+                        "collaborator": [],
+                        "commit": [],
+                        "ticket": [],
+                    },
+                    "access_users": {
+                        "admin": ["foo"],
+                        "collaborator": [],
+                        "commit": [],
+                        "owner": ["pingou"],
+                        "ticket": [],
+                    },
+                    "close_status": [
+                        "Invalid",
+                        "Insufficient data",
+                        "Fixed",
+                        "Duplicate",
+                    ],
+                    "custom_keys": [],
+                    "date_created": ANY,
+                    "date_modified": ANY,
+                    "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
+                    "fullname": "test",
+                    "id": 1,
+                    "milestones": {},
+                    "name": "test",
+                    "namespace": None,
+                    "parent": None,
+                    "priorities": {},
+                    "tags": [],
+                    "url_path": "test",
+                    "user": {
+                        "full_url": "http://localhost.localdomain/user/pingou",
+                        "fullname": "PY C",
+                        "name": "pingou",
+                        "url_path": "user/pingou",
+                    },
+                },
+                "user": "foo",
+            },
+            "hascommit": True,
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_admin_user_and_commit(self):
+
+        tests.create_user(self.session, "baz", "foo baz", ["baz@bar.com"])
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="foo",
+            user="pingou",
+            access="admin",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="baz",
+            user="pingou",
+            access="commit",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        output = self.app.get("/api/0/test/hascommit?user=baz&branch=main")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "args": {
+                "branch": "main",
+                "project": {
+                    "access_groups": {
+                        "admin": [],
+                        "collaborator": [],
+                        "commit": [],
+                        "ticket": [],
+                    },
+                    "access_users": {
+                        "admin": ["foo"],
+                        "collaborator": [],
+                        "commit": ["baz"],
+                        "owner": ["pingou"],
+                        "ticket": [],
+                    },
+                    "close_status": [
+                        "Invalid",
+                        "Insufficient data",
+                        "Fixed",
+                        "Duplicate",
+                    ],
+                    "custom_keys": [],
+                    "date_created": ANY,
+                    "date_modified": ANY,
+                    "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
+                    "fullname": "test",
+                    "id": 1,
+                    "milestones": {},
+                    "name": "test",
+                    "namespace": None,
+                    "parent": None,
+                    "priorities": {},
+                    "tags": [],
+                    "url_path": "test",
+                    "user": {
+                        "full_url": "http://localhost.localdomain/user/pingou",
+                        "fullname": "PY C",
+                        "name": "pingou",
+                        "url_path": "user/pingou",
+                    },
+                },
+                "user": "baz",
+            },
+            "hascommit": True,
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_admin_user_and_commit_and_ticket(self):
+
+        tests.create_user(self.session, "baz", "foo baz", ["baz@bar.com"])
+        tests.create_user(
+            self.session, "alex", "Alex Ander", ["alex@ander.com"]
+        )
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="foo",
+            user="pingou",
+            access="admin",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="baz",
+            user="pingou",
+            access="commit",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="alex",
+            user="pingou",
+            access="ticket",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        output = self.app.get("/api/0/test/hascommit?user=alex&branch=main")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "args": {
+                "branch": "main",
+                "project": {
+                    "access_groups": {
+                        "admin": [],
+                        "collaborator": [],
+                        "commit": [],
+                        "ticket": [],
+                    },
+                    "access_users": {
+                        "admin": ["foo"],
+                        "collaborator": [],
+                        "commit": ["baz"],
+                        "owner": ["pingou"],
+                        "ticket": ["alex"],
+                    },
+                    "close_status": [
+                        "Invalid",
+                        "Insufficient data",
+                        "Fixed",
+                        "Duplicate",
+                    ],
+                    "custom_keys": [],
+                    "date_created": ANY,
+                    "date_modified": ANY,
+                    "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
+                    "fullname": "test",
+                    "id": 1,
+                    "milestones": {},
+                    "name": "test",
+                    "namespace": None,
+                    "parent": None,
+                    "priorities": {},
+                    "tags": [],
+                    "url_path": "test",
+                    "user": {
+                        "full_url": "http://localhost.localdomain/user/pingou",
+                        "fullname": "PY C",
+                        "name": "pingou",
+                        "url_path": "user/pingou",
+                    },
+                },
+                "user": "alex",
+            },
+            "hascommit": False,
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+    def test_admin_user_and_commit_and_ticket_and_contributors(self):
+
+        tests.create_user(self.session, "baz", "foo baz", ["baz@bar.com"])
+        tests.create_user(
+            self.session, "alex", "Alex Ander", ["alex@ander.com"]
+        )
+        tests.create_user(self.session, "ralph", "Ralph B.", ["ralph@b.com"])
+        tests.create_user(self.session, "kevin", "Kevin F.", ["kevin@f.com"])
+
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="foo",
+            user="pingou",
+            access="admin",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="baz",
+            user="pingou",
+            access="commit",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="alex",
+            user="pingou",
+            access="ticket",
+            branches=None,
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="ralph",
+            user="pingou",
+            access="collaborator",
+            branches="epel*",
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        msg = pagure.lib.query.add_user_to_project(
+            session=self.session,
+            project=project,
+            new_user="kevin",
+            user="pingou",
+            access="collaborator",
+            branches="f*",
+            required_groups=None,
+        )
+        self.session.commit()
+        self.assertEqual(msg, "User added")
+
+        output = self.app.get("/api/0/test/hascommit?user=kevin&branch=main")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "args": {
+                "branch": "main",
+                "project": {
+                    "access_groups": {
+                        "admin": [],
+                        "collaborator": [],
+                        "commit": [],
+                        "ticket": [],
+                    },
+                    "access_users": {
+                        "admin": ["foo"],
+                        "collaborator": ["kevin", "ralph"],
+                        "commit": ["baz"],
+                        "owner": ["pingou"],
+                        "ticket": ["alex"],
+                    },
+                    "close_status": [
+                        "Invalid",
+                        "Insufficient data",
+                        "Fixed",
+                        "Duplicate",
+                    ],
+                    "custom_keys": [],
+                    "date_created": ANY,
+                    "date_modified": ANY,
+                    "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
+                    "fullname": "test",
+                    "id": 1,
+                    "milestones": {},
+                    "name": "test",
+                    "namespace": None,
+                    "parent": None,
+                    "priorities": {},
+                    "tags": [],
+                    "url_path": "test",
+                    "user": {
+                        "full_url": "http://localhost.localdomain/user/pingou",
+                        "fullname": "PY C",
+                        "name": "pingou",
+                        "url_path": "user/pingou",
+                    },
+                },
+                "user": "kevin",
+            },
+            "hascommit": False,
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
+
+        output = self.app.get("/api/0/test/hascommit?user=kevin&branch=f33")
+        self.assertEqual(output.status_code, 200)
+        expected_rv = {
+            "args": {
+                "branch": "f33",
+                "project": {
+                    "access_groups": {
+                        "admin": [],
+                        "collaborator": [],
+                        "commit": [],
+                        "ticket": [],
+                    },
+                    "access_users": {
+                        "admin": ["foo"],
+                        "collaborator": ["kevin", "ralph"],
+                        "commit": ["baz"],
+                        "owner": ["pingou"],
+                        "ticket": ["alex"],
+                    },
+                    "close_status": [
+                        "Invalid",
+                        "Insufficient data",
+                        "Fixed",
+                        "Duplicate",
+                    ],
+                    "custom_keys": [],
+                    "date_created": ANY,
+                    "date_modified": ANY,
+                    "description": "test project #1",
+                    "full_url": "http://localhost.localdomain/test",
+                    "fullname": "test",
+                    "id": 1,
+                    "milestones": {},
+                    "name": "test",
+                    "namespace": None,
+                    "parent": None,
+                    "priorities": {},
+                    "tags": [],
+                    "url_path": "test",
+                    "user": {
+                        "full_url": "http://localhost.localdomain/user/pingou",
+                        "fullname": "PY C",
+                        "name": "pingou",
+                        "url_path": "user/pingou",
+                    },
+                },
+                "user": "kevin",
+            },
+            "hascommit": True,
+        }
+        data = json.loads(output.get_data(as_text=True))
+        self.assertDictEqual(data, expected_rv)
diff --git a/tests/test_pagure_flask_api_project_tags.py b/tests/test_pagure_flask_api_project_tags.py
index 6b57a47..64a4a9d 100644
--- a/tests/test_pagure_flask_api_project_tags.py
+++ b/tests/test_pagure_flask_api_project_tags.py
@@ -20,10 +20,10 @@ import pagure.lib.query
 
 
 class PagureFlaskApiProjectTagstests(tests.Modeltests):
-    """ Tests for the flask API of pagure project tags """
+    """Tests for the flask API of pagure project tags"""
 
     def test_api_project_tags_no_project(self):
-        """ Test the api_project_tags function.  """
+        """Test the api_project_tags function."""
         output = self.app.get("/api/0/foo/tags/")
         self.assertEqual(output.status_code, 404)
         expected_rv = {
@@ -34,7 +34,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tags(self):
-        """ Test the api_project_tags function.  """
+        """Test the api_project_tags function."""
         tests.create_projects(self.session)
 
         output = self.app.get("/api/0/test/tags/")
@@ -73,7 +73,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertEqual(data["total_tags"], 0)
 
     def test_api_project_tags_new_wrong_token(self):
-        """ Test the api_tags_new method of the flask api. """
+        """Test the api_tags_new method of the flask api."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -92,7 +92,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tags_new_wrong_project(self):
-        """ Test the api_tags_new method of the flask api. """
+        """Test the api_tags_new method of the flask api."""
 
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
@@ -109,7 +109,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tags_new_wrong_acls(self):
-        """ Test the api_tags_new method of the flask api. """
+        """Test the api_tags_new method of the flask api."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session, acl_name="create_project")
@@ -127,7 +127,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tags_new_no_input(self):
-        """ Test the api_tags_new method of the flask api. """
+        """Test the api_tags_new method of the flask api."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -146,7 +146,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tags_new(self):
-        """ Test the api_tags_new method of the flask api. """
+        """Test the api_tags_new method of the flask api."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -181,7 +181,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tags_new_existing_tag(self):
-        """ Test the api_tags_new method of the flask api. """
+        """Test the api_tags_new method of the flask api."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -219,7 +219,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tag_delete_wrong_token(self):
-        """ Test the api_project_tag_delete method of flask api. """
+        """Test the api_project_tag_delete method of flask api."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -238,7 +238,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tag_delete_wrong_project(self):
-        """ Test the api_project_tag_delete method of flask api. """
+        """Test the api_project_tag_delete method of flask api."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -254,7 +254,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tag_delete_wrong_tag(self):
-        """ Test the api_project_tag_delete method of flask api. """
+        """Test the api_project_tag_delete method of flask api."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -267,7 +267,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tag_delete(self):
-        """ Test the api_project_tag_delete method of flask api. """
+        """Test the api_project_tag_delete method of flask api."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -296,7 +296,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tag_delete_with_assigned_issue_and_pr(self):
-        """ Test the api_project_tag_delete method of flask api. """
+        """Test the api_project_tag_delete method of flask api."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -360,7 +360,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tag_view_no_project(self):
-        """ Test the api_project_tag_view method of the flask api.  """
+        """Test the api_project_tag_view method of the flask api."""
         output = self.app.get("/api/0/foo/tag/tag1")
         self.assertEqual(output.status_code, 404)
         expected_rv = {
@@ -371,7 +371,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tag_view_wrong_tag(self):
-        """ Test the api_project_tag_view method of the flask api.  """
+        """Test the api_project_tag_view method of the flask api."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -388,7 +388,7 @@ class PagureFlaskApiProjectTagstests(tests.Modeltests):
         self.assertDictEqual(data, expected_rv)
 
     def test_api_project_tag_view(self):
-        """ Test the api_project_tag_view method of the flask api.  """
+        """Test the api_project_tag_view method of the flask api."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
diff --git a/tests/test_pagure_flask_api_project_update_watch.py b/tests/test_pagure_flask_api_project_update_watch.py
index d69616d..3025413 100644
--- a/tests/test_pagure_flask_api_project_update_watch.py
+++ b/tests/test_pagure_flask_api_project_update_watch.py
@@ -30,13 +30,13 @@ import tests
 
 
 class PagureFlaskApiProjectUpdateWatchTests(tests.Modeltests):
-    """ Tests for the flask API of pagure for changing the watch status on
+    """Tests for the flask API of pagure for changing the watch status on
     a project via the API
     """
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiProjectUpdateWatchTests, self).setUp()
 
         tests.create_projects(self.session)
@@ -78,7 +78,7 @@ class PagureFlaskApiProjectUpdateWatchTests(tests.Modeltests):
         tests.create_tokens_acl(self.session, token_id="project-less-foo")
 
     def test_api_update_project_watchers_invalid_project(self):
-        """ Test the api_update_project_watchers method of the flask api. """
+        """Test the api_update_project_watchers method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -93,7 +93,7 @@ class PagureFlaskApiProjectUpdateWatchTests(tests.Modeltests):
         )
 
     def test_api_change_status_issue_token_not_for_project(self):
-        """ Test the api_update_project_watchers method of the flask api. """
+        """Test the api_update_project_watchers method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -107,7 +107,7 @@ class PagureFlaskApiProjectUpdateWatchTests(tests.Modeltests):
         self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])
 
     def test_api_update_project_watchers_no_user_watching(self):
-        """ Test the api_update_project_watchers method of the flask api. """
+        """Test the api_update_project_watchers method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {"status": "42"}
@@ -126,7 +126,7 @@ class PagureFlaskApiProjectUpdateWatchTests(tests.Modeltests):
         )
 
     def test_api_update_project_watchers_no_watch_status(self):
-        """ Test the api_update_project_watchers method of the flask api. """
+        """Test the api_update_project_watchers method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {"watcher": "pingou"}
@@ -145,7 +145,7 @@ class PagureFlaskApiProjectUpdateWatchTests(tests.Modeltests):
         )
 
     def test_api_update_project_watchers_invalid_status(self):
-        """ Test the api_update_project_watchers method of the flask api. """
+        """Test the api_update_project_watchers method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {"watcher": "pingou", "status": "42"}
@@ -164,7 +164,7 @@ class PagureFlaskApiProjectUpdateWatchTests(tests.Modeltests):
         )
 
     def test_api_update_project_watchers_invalid_user(self):
-        """ Test the api_update_project_watchers method of the flask api. """
+        """Test the api_update_project_watchers method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {"watcher": "example", "status": "2"}
@@ -183,7 +183,7 @@ class PagureFlaskApiProjectUpdateWatchTests(tests.Modeltests):
         )
 
     def test_api_update_project_watchers_other_user(self):
-        """ Test the api_update_project_watchers method of the flask api. """
+        """Test the api_update_project_watchers method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {"watcher": "foo", "status": "2"}
@@ -202,7 +202,7 @@ class PagureFlaskApiProjectUpdateWatchTests(tests.Modeltests):
         )
 
     def test_api_update_project_watchers_all_good(self):
-        """ Test the api_update_project_watchers method of the flask api. """
+        """Test the api_update_project_watchers method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {"watcher": "pingou", "status": 1}
@@ -222,7 +222,7 @@ class PagureFlaskApiProjectUpdateWatchTests(tests.Modeltests):
 
     @patch("pagure.utils.is_admin", MagicMock(return_value=True))
     def test_api_update_project_watchers_other_user_admin(self):
-        """ Test the api_update_project_watchers method of the flask api. """
+        """Test the api_update_project_watchers method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {"watcher": "foo", "status": "2"}
@@ -242,7 +242,7 @@ class PagureFlaskApiProjectUpdateWatchTests(tests.Modeltests):
 
     @patch("pagure.utils.is_admin", MagicMock(return_value=True))
     def test_api_update_project_watchers_set_then_reset(self):
-        """ Test the api_update_project_watchers method of the flask api. """
+        """Test the api_update_project_watchers method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd_project_less"}
         data = {"watcher": "foo", "status": "2"}
@@ -268,12 +268,13 @@ class PagureFlaskApiProjectUpdateWatchTests(tests.Modeltests):
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
         self.assertDictEqual(
-            data, {"message": "Watch status reset", "status": "ok"},
+            data,
+            {"message": "Watch status reset", "status": "ok"},
         )
 
     @patch("pagure.utils.is_admin", MagicMock(return_value=True))
     def test_api_update_project_watchers_invalid_user_admin(self):
-        """ Test the api_update_project_watchers method of the flask api. """
+        """Test the api_update_project_watchers method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {"watcher": "example", "status": "2"}
@@ -293,7 +294,7 @@ class PagureFlaskApiProjectUpdateWatchTests(tests.Modeltests):
 
     @patch("pagure.utils.is_admin", MagicMock(return_value=True))
     def test_api_update_project_watchers_missing_user_admin(self):
-        """ Test the api_update_project_watchers method of the flask api. """
+        """Test the api_update_project_watchers method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
         data = {"status": "2"}
diff --git a/tests/test_pagure_flask_api_project_view_file.py b/tests/test_pagure_flask_api_project_view_file.py
index 708a968..a05e16f 100644
--- a/tests/test_pagure_flask_api_project_view_file.py
+++ b/tests/test_pagure_flask_api_project_view_file.py
@@ -33,7 +33,7 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureFlaskApiProjectViewFiletests(tests.Modeltests):
-    """ Tests for the flask API of pagure for issue """
+    """Tests for the flask API of pagure for issue"""
 
     maxDiff = None
 
diff --git a/tests/test_pagure_flask_api_ui_private_repo.py b/tests/test_pagure_flask_api_ui_private_repo.py
index b4a56eb..f0aba34 100644
--- a/tests/test_pagure_flask_api_ui_private_repo.py
+++ b/tests/test_pagure_flask_api_ui_private_repo.py
@@ -33,6 +33,7 @@ FULL_ISSUE_LIST = [
         "custom_fields": [],
         "date_created": "1431414800",
         "depends": [],
+        "full_url": "http://localhost.localdomain/test4/issue/8",
         "id": 8,
         "last_updated": "1431414800",
         "milestone": None,
@@ -44,6 +45,7 @@ FULL_ISSUE_LIST = [
         "title": "Test issue",
         "user": {
             "fullname": "PY C",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "name": "pingou",
             "url_path": "user/pingou",
         },
@@ -59,6 +61,7 @@ FULL_ISSUE_LIST = [
         "custom_fields": [],
         "date_created": "1431414800",
         "depends": [],
+        "full_url": "http://localhost.localdomain/test4/issue/7",
         "id": 7,
         "last_updated": "1431414800",
         "milestone": None,
@@ -71,6 +74,7 @@ FULL_ISSUE_LIST = [
         "user": {
             "fullname": "PY C",
             "name": "pingou",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "url_path": "user/pingou",
         },
     },
@@ -85,6 +89,7 @@ FULL_ISSUE_LIST = [
         "custom_fields": [],
         "date_created": "1431414800",
         "depends": [],
+        "full_url": "http://localhost.localdomain/test4/issue/6",
         "id": 6,
         "last_updated": "1431414800",
         "milestone": None,
@@ -97,6 +102,7 @@ FULL_ISSUE_LIST = [
         "user": {
             "fullname": "PY C",
             "name": "pingou",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "url_path": "user/pingou",
         },
     },
@@ -111,6 +117,7 @@ FULL_ISSUE_LIST = [
         "custom_fields": [],
         "date_created": "1431414800",
         "depends": [],
+        "full_url": "http://localhost.localdomain/test4/issue/5",
         "id": 5,
         "last_updated": "1431414800",
         "milestone": None,
@@ -123,6 +130,7 @@ FULL_ISSUE_LIST = [
         "user": {
             "fullname": "PY C",
             "name": "pingou",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "url_path": "user/pingou",
         },
     },
@@ -137,6 +145,7 @@ FULL_ISSUE_LIST = [
         "custom_fields": [],
         "date_created": "1431414800",
         "depends": [],
+        "full_url": "http://localhost.localdomain/test4/issue/4",
         "id": 4,
         "last_updated": "1431414800",
         "milestone": None,
@@ -149,6 +158,7 @@ FULL_ISSUE_LIST = [
         "user": {
             "fullname": "PY C",
             "name": "pingou",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "url_path": "user/pingou",
         },
     },
@@ -163,6 +173,7 @@ FULL_ISSUE_LIST = [
         "custom_fields": [],
         "date_created": "1431414800",
         "depends": [],
+        "full_url": "http://localhost.localdomain/test4/issue/3",
         "id": 3,
         "last_updated": "1431414800",
         "milestone": None,
@@ -175,6 +186,7 @@ FULL_ISSUE_LIST = [
         "user": {
             "fullname": "PY C",
             "name": "pingou",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "url_path": "user/pingou",
         },
     },
@@ -189,6 +201,7 @@ FULL_ISSUE_LIST = [
         "custom_fields": [],
         "date_created": "1431414800",
         "depends": [],
+        "full_url": "http://localhost.localdomain/test4/issue/2",
         "id": 2,
         "last_updated": "1431414800",
         "milestone": None,
@@ -201,6 +214,7 @@ FULL_ISSUE_LIST = [
         "user": {
             "fullname": "PY C",
             "name": "pingou",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "url_path": "user/pingou",
         },
     },
@@ -215,6 +229,7 @@ FULL_ISSUE_LIST = [
         "custom_fields": [],
         "date_created": "1431414800",
         "depends": [],
+        "full_url": "http://localhost.localdomain/test4/issue/1",
         "id": 1,
         "last_updated": "1431414800",
         "milestone": None,
@@ -227,6 +242,7 @@ FULL_ISSUE_LIST = [
         "user": {
             "fullname": "PY C",
             "name": "pingou",
+            "full_url": "http://localhost.localdomain/user/pingou",
             "url_path": "user/pingou",
         },
     },
@@ -234,12 +250,12 @@ FULL_ISSUE_LIST = [
 
 
 class PagurePrivateRepotest(tests.Modeltests):
-    """ Tests for private repo in pagure """
+    """Tests for private repo in pagure"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagurePrivateRepotest, self).setUp()
 
         pagure.config.config["TESTING"] = True
@@ -250,7 +266,7 @@ class PagurePrivateRepotest(tests.Modeltests):
     def set_up_git_repo(
         self, new_project=None, branch_from="feature", mtype="FF"
     ):
-        """ Set up the git repo and create the corresponding PullRequest
+        """Set up the git repo and create the corresponding PullRequest
         object.
         """
 
@@ -395,7 +411,7 @@ class PagurePrivateRepotest(tests.Modeltests):
         shutil.rmtree(newpath)
 
     def test_index(self):
-        """ Test the index endpoint. """
+        """Test the index endpoint."""
 
         output = self.app.get("/")
         self.assertEqual(output.status_code, 200)
@@ -453,7 +469,7 @@ class PagurePrivateRepotest(tests.Modeltests):
             )
 
     def test_view_user(self):
-        """ Test the view_user endpoint. """
+        """Test the view_user endpoint."""
 
         output = self.app.get("/user/foo?repopage=abc&forkpage=def")
         self.assertEqual(output.status_code, 200)
@@ -687,7 +703,7 @@ class PagurePrivateRepotest(tests.Modeltests):
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_private_settings_ui(self, ast):
-        """ Test UI for private repo"""
+        """Test UI for private repo"""
         ast.return_value = False
 
         # Add private repo
@@ -742,7 +758,7 @@ class PagurePrivateRepotest(tests.Modeltests):
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_private_settings_ui_update_privacy_false(self, ast):
-        """ Test UI for private repo"""
+        """Test UI for private repo"""
         ast.return_value = False
 
         # Add private repo
@@ -799,7 +815,7 @@ class PagurePrivateRepotest(tests.Modeltests):
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_private_settings_ui_update_privacy_true(self, ast):
-        """ Test UI for private repo"""
+        """Test UI for private repo"""
         ast.return_value = False
 
         # Add private repo
@@ -964,7 +980,7 @@ class PagurePrivateRepotest(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_private_repo_issues_ui(self, p_send_email, p_ugt):
-        """ Test issues made to private repo"""
+        """Test issues made to private repo"""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1070,7 +1086,7 @@ class PagurePrivateRepotest(tests.Modeltests):
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_private_repo_ui_for_different_repo_user(self, ast):
-        """ Test the private repo for different ACLS"""
+        """Test the private repo for different ACLS"""
         ast.return_value = False
 
         # Add private repo
@@ -1167,7 +1183,7 @@ class PagurePrivateRepotest(tests.Modeltests):
 
     # API checks
     def test_api_private_repo_projects(self):
-        """ Test api points for private repo for projects"""
+        """Test api points for private repo for projects"""
 
         # Add private repo
         item = pagure.lib.model.Project(
@@ -1412,6 +1428,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                             "date_created": "1436527638",
                             "date_modified": "1436527638",
                             "description": "test project description",
+                            "full_url": "http://localhost.localdomain/test4",
                             "id": 1,
                             "milestones": {},
                             "name": "test4",
@@ -1424,6 +1441,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                             "user": {
                                 "fullname": "PY C",
                                 "name": "pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
                                 "url_path": "user/pingou",
                             },
                         }
@@ -1472,6 +1490,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                             "date_created": "1436527638",
                             "date_modified": "1436527638",
                             "description": "test project description",
+                            "full_url": "http://localhost.localdomain/test4",
                             "id": 1,
                             "milestones": {},
                             "name": "test4",
@@ -1484,6 +1503,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                             "user": {
                                 "fullname": "PY C",
                                 "name": "pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
                                 "url_path": "user/pingou",
                             },
                         }
@@ -1494,7 +1514,7 @@ class PagurePrivateRepotest(tests.Modeltests):
     # Api pull-request views
     @patch("pagure.lib.notify.send_email")
     def test_api_private_repo_fork(self, send_email):
-        """ Test api endpoints in api/fork"""
+        """Test api endpoints in api/fork"""
 
         send_email.return_value = True
 
@@ -1586,6 +1606,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                             "commit_start": None,
                             "commit_stop": None,
                             "date_created": "1431414800",
+                            "full_url": "http://localhost.localdomain/test4/pull-request/1",
                             "last_updated": "1431414800",
                             "id": 1,
                             "initial_comment": None,
@@ -1608,6 +1629,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                                 "date_created": "1431414800",
                                 "date_modified": "1431414800",
                                 "description": "test project description",
+                                "full_url": "http://localhost.localdomain/test4",
                                 "id": 1,
                                 "milestones": {},
                                 "name": "test4",
@@ -1619,6 +1641,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                                 "tags": [],
                                 "user": {
                                     "fullname": "PY C",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
                                     "name": "pingou",
                                     "url_path": "user/pingou",
                                 },
@@ -1643,6 +1666,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                                 "date_created": "1431414800",
                                 "date_modified": "1431414800",
                                 "description": "test project description",
+                                "full_url": "http://localhost.localdomain/test4",
                                 "id": 1,
                                 "milestones": {},
                                 "fullname": "test4",
@@ -1655,6 +1679,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                                 "user": {
                                     "fullname": "PY C",
                                     "name": "pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
                                     "url_path": "user/pingou",
                                 },
                             },
@@ -1666,6 +1691,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                             "updated_on": "1431414800",
                             "user": {
                                 "fullname": "PY C",
+                                "full_url": "http://localhost.localdomain/user/pingou",
                                 "name": "pingou",
                                 "url_path": "user/pingou",
                             },
@@ -1721,6 +1747,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                     "commit_start": None,
                     "commit_stop": None,
                     "date_created": "1431414800",
+                    "full_url": "http://localhost.localdomain/test4/pull-request/1",
                     "last_updated": "1431414800",
                     "id": 1,
                     "initial_comment": None,
@@ -1743,6 +1770,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                         "date_created": "1431414800",
                         "date_modified": "1431414800",
                         "description": "test project description",
+                        "full_url": "http://localhost.localdomain/test4",
                         "id": 1,
                         "milestones": {},
                         "name": "test4",
@@ -1754,6 +1782,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                         "tags": [],
                         "user": {
                             "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
                             "name": "pingou",
                             "url_path": "user/pingou",
                         },
@@ -1778,6 +1807,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                         "date_created": "1431414800",
                         "date_modified": "1431414800",
                         "description": "test project description",
+                        "full_url": "http://localhost.localdomain/test4",
                         "id": 1,
                         "milestones": {},
                         "name": "test4",
@@ -1789,6 +1819,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                         "tags": [],
                         "user": {
                             "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
                             "name": "pingou",
                             "url_path": "user/pingou",
                         },
@@ -1802,6 +1833,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                     "user": {
                         "fullname": "PY C",
                         "name": "pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "url_path": "user/pingou",
                     },
                 },
@@ -1826,7 +1858,7 @@ class PagurePrivateRepotest(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_pr_private_repo_add_comment(self, mockemail):
-        """ Test the api_pull_request_add_comment method of the flask api. """
+        """Test the api_pull_request_add_comment method of the flask api."""
         mockemail.return_value = True
         pagure.config.config["REQUESTS_FOLDER"] = None
 
@@ -1910,7 +1942,7 @@ class PagurePrivateRepotest(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_private_repo_pr_add_flag(self, mockemail):
-        """ Test the api_pull_request_add_flag method of the flask api. """
+        """Test the api_pull_request_add_flag method of the flask api."""
         mockemail.return_value = True
         pagure.config.config["REQUESTS_FOLDER"] = None
 
@@ -1985,6 +2017,8 @@ class PagurePrivateRepotest(tests.Modeltests):
             title="test pull-request",
             user="pingou",
         )
+        req.commit_stop = "hash_commit_stop"
+        self.session.add(req)
         self.session.commit()
         self.assertEqual(req.id, 1)
         self.assertEqual(req.title, "test pull-request")
@@ -2041,22 +2075,22 @@ class PagurePrivateRepotest(tests.Modeltests):
         data = json.loads(output.get_data(as_text=True))
         data["flag"]["date_created"] = "1510742565"
         data["flag"]["date_updated"] = "1510742565"
-        data["flag"]["pull_request_uid"] = "62b49f00d489452994de5010565fab81"
         data["avatar_url"] = "https://seccdn.libravatar.org/avatar/..."
         self.assertDictEqual(
             data,
             {
                 "flag": {
                     "comment": "Tests failed",
+                    "commit_hash": "hash_commit_stop",
                     "date_created": "1510742565",
                     "date_updated": "1510742565",
                     "percent": 0,
-                    "pull_request_uid": "62b49f00d489452994de5010565fab81",
                     "status": "failure",
                     "url": "http://jenkins.cloud.fedoraproject.org/",
                     "user": {
                         "default_email": "bar@pingou.com",
                         "emails": ["bar@pingou.com", "foo@pingou.com"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -2075,9 +2109,14 @@ class PagurePrivateRepotest(tests.Modeltests):
         request = pagure.lib.query.search_pull_requests(
             self.session, project_id=1, requestid=1
         )
-        self.assertEqual(len(request.flags), 1)
-        self.assertEqual(request.flags[0].comment, "Tests failed")
-        self.assertEqual(request.flags[0].percent, 0)
+        self.assertEqual(len(request.flags), 0)
+
+        flags = pagure.lib.query.get_commit_flag(
+            self.session, request.project, "hash_commit_stop"
+        )
+        self.assertEqual(len(flags), 1)
+        self.assertEqual(flags[0].comment, "Tests failed")
+        self.assertEqual(flags[0].percent, 0)
 
         # Update flag
         data = {
@@ -2095,22 +2134,22 @@ class PagurePrivateRepotest(tests.Modeltests):
         data = json.loads(output.get_data(as_text=True))
         data["flag"]["date_created"] = "1510742565"
         data["flag"]["date_updated"] = "1510742565"
-        data["flag"]["pull_request_uid"] = "62b49f00d489452994de5010565fab81"
         data["avatar_url"] = "https://seccdn.libravatar.org/avatar/..."
         self.assertDictEqual(
             data,
             {
                 "flag": {
                     "comment": "Tests passed",
+                    "commit_hash": "hash_commit_stop",
                     "date_created": "1510742565",
                     "date_updated": "1510742565",
                     "percent": 100,
-                    "pull_request_uid": "62b49f00d489452994de5010565fab81",
                     "status": "success",
                     "url": "http://jenkins.cloud.fedoraproject.org/",
                     "user": {
                         "default_email": "bar@pingou.com",
                         "emails": ["bar@pingou.com", "foo@pingou.com"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
@@ -2129,13 +2168,18 @@ class PagurePrivateRepotest(tests.Modeltests):
         request = pagure.lib.query.search_pull_requests(
             self.session, project_id=1, requestid=1
         )
-        self.assertEqual(len(request.flags), 1)
-        self.assertEqual(request.flags[0].comment, "Tests passed")
-        self.assertEqual(request.flags[0].percent, 100)
+        self.assertEqual(len(request.flags), 0)
+
+        flags = pagure.lib.query.get_commit_flag(
+            self.session, request.project, "hash_commit_stop"
+        )
+        self.assertEqual(len(flags), 1)
+        self.assertEqual(flags[0].comment, "Tests passed")
+        self.assertEqual(flags[0].percent, 100)
 
     @patch("pagure.lib.notify.send_email")
     def test_api_private_repo_pr_close(self, send_email):
-        """ Test the api_pull_request_close method of the flask api. """
+        """Test the api_pull_request_close method of the flask api."""
         send_email.return_value = True
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -2261,7 +2305,7 @@ class PagurePrivateRepotest(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_private_repo_pr_merge(self, send_email):
-        """ Test the api_pull_request_merge method of the flask api. """
+        """Test the api_pull_request_merge method of the flask api."""
         send_email.return_value = True
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -2397,7 +2441,7 @@ class PagurePrivateRepotest(tests.Modeltests):
         self.assertDictEqual(data, {"message": "Changes merged!"})
 
     def test_api_private_repo_new_issue(self):
-        """ Test the api_new_issue method of the flask api. """
+        """Test the api_new_issue method of the flask api."""
         # Add private repo
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
@@ -2508,7 +2552,7 @@ class PagurePrivateRepotest(tests.Modeltests):
         )
 
     def test_api_private_repo_view_issues(self):
-        """ Test the api_view_issues method of the flask api. """
+        """Test the api_view_issues method of the flask api."""
         self.test_api_private_repo_new_issue()
 
         # Invalid repo
@@ -2559,6 +2603,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                             "date_created": "1431414800",
                             "last_updated": "1431414800",
                             "depends": [],
+                            "full_url": "http://localhost.localdomain/test4/issue/1",
                             "id": 1,
                             "milestone": None,
                             "priority": None,
@@ -2569,6 +2614,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                             "title": "test issue",
                             "user": {
                                 "fullname": "PY C",
+                                "full_url": "http://localhost.localdomain/user/pingou",
                                 "name": "pingou",
                                 "url_path": "user/pingou",
                             },
@@ -2640,6 +2686,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                             "date_created": "1431414800",
                             "last_updated": "1431414800",
                             "depends": [],
+                            "full_url": "http://localhost.localdomain/test4/issue/2",
                             "id": 2,
                             "milestone": None,
                             "priority": None,
@@ -2650,6 +2697,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                             "title": "Test issue",
                             "user": {
                                 "fullname": "PY C",
+                                "full_url": "http://localhost.localdomain/user/pingou",
                                 "name": "pingou",
                                 "url_path": "user/pingou",
                             },
@@ -2666,6 +2714,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                             "date_created": "1431414800",
                             "last_updated": "1431414800",
                             "depends": [],
+                            "full_url": "http://localhost.localdomain/test4/issue/1",
                             "id": 1,
                             "milestone": None,
                             "priority": None,
@@ -2676,6 +2725,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                             "title": "test issue",
                             "user": {
                                 "fullname": "PY C",
+                                "full_url": "http://localhost.localdomain/user/pingou",
                                 "name": "pingou",
                                 "url_path": "user/pingou",
                             },
@@ -2740,6 +2790,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                         "date_created": "1431414800",
                         "last_updated": "1431414800",
                         "depends": [],
+                        "full_url": "http://localhost.localdomain/test4/issue/2",
                         "id": 2,
                         "milestone": None,
                         "priority": None,
@@ -2750,6 +2801,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                         "title": "Test issue",
                         "user": {
                             "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
                             "name": "pingou",
                             "url_path": "user/pingou",
                         },
@@ -2766,6 +2818,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                         "date_created": "1431414800",
                         "last_updated": "1431414800",
                         "depends": [],
+                        "full_url": "http://localhost.localdomain/test4/issue/1",
                         "id": 1,
                         "milestone": None,
                         "priority": None,
@@ -2776,6 +2829,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                         "title": "test issue",
                         "user": {
                             "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
                             "name": "pingou",
                             "url_path": "user/pingou",
                         },
@@ -2911,6 +2965,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                         "date_created": "1431414800",
                         "last_updated": "1431414800",
                         "depends": [],
+                        "full_url": "http://localhost.localdomain/test4/issue/2",
                         "id": 2,
                         "milestone": None,
                         "priority": None,
@@ -2921,6 +2976,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                         "title": "Test issue",
                         "user": {
                             "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
                             "name": "pingou",
                             "url_path": "user/pingou",
                         },
@@ -2937,6 +2993,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                         "date_created": "1431414800",
                         "last_updated": "1431414800",
                         "depends": [],
+                        "full_url": "http://localhost.localdomain/test4/issue/1",
                         "id": 1,
                         "milestone": None,
                         "priority": None,
@@ -2947,6 +3004,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                         "title": "test issue",
                         "user": {
                             "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
                             "name": "pingou",
                             "url_path": "user/pingou",
                         },
@@ -2966,7 +3024,7 @@ class PagurePrivateRepotest(tests.Modeltests):
         )
 
     def test_api_pivate_repo_view_issue(self):
-        """ Test the api_view_issue method of the flask api. """
+        """Test the api_view_issue method of the flask api."""
         self.test_api_private_repo_new_issue()
 
         # Invalid repo
@@ -3017,6 +3075,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                     "custom_fields": [],
                     "date_created": "1431414800",
                     "depends": [],
+                    "full_url": "http://localhost.localdomain/test4/issue/1",
                     "id": 1,
                     "last_updated": "1431414800",
                     "milestone": None,
@@ -3028,6 +3087,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                     "title": "test issue",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -3070,6 +3130,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                 "custom_fields": [],
                 "date_created": "1431414800",
                 "depends": [],
+                "full_url": "http://localhost.localdomain/test4/issue/1",
                 "id": 1,
                 "last_updated": "1431414800",
                 "milestone": None,
@@ -3081,6 +3142,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                 "title": "test issue",
                 "user": {
                     "fullname": "PY C",
+                    "full_url": "http://localhost.localdomain/user/pingou",
                     "name": "pingou",
                     "url_path": "user/pingou",
                 },
@@ -3089,7 +3151,7 @@ class PagurePrivateRepotest(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_private_repo_change_status_issue(self):
-        """ Test the api_change_status_issue method of the flask api. """
+        """Test the api_change_status_issue method of the flask api."""
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
             name="test4",
@@ -3231,7 +3293,7 @@ class PagurePrivateRepotest(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_api_private_repo_comment_issue(self, p_send_email, p_ugt):
-        """ Test the api_comment_issue method of the flask api. """
+        """Test the api_comment_issue method of the flask api."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3350,7 +3412,7 @@ class PagurePrivateRepotest(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_api_view_issue_comment(self, p_send_email, p_ugt):
-        """ Test the api_view_issue_comment endpoint. """
+        """Test the api_view_issue_comment endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3391,6 +3453,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                     "reactions": {},
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -3419,6 +3482,7 @@ class PagurePrivateRepotest(tests.Modeltests):
                     "reactions": {},
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
diff --git a/tests/test_pagure_flask_api_user.py b/tests/test_pagure_flask_api_user.py
index 7e27638..495f482 100644
--- a/tests/test_pagure_flask_api_user.py
+++ b/tests/test_pagure_flask_api_user.py
@@ -32,18 +32,18 @@ import tests
 
 
 class PagureFlaskApiUSertests(tests.Modeltests):
-    """ Tests for the flask API of pagure for issue """
+    """Tests for the flask API of pagure for issue"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiUSertests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
 
     def test_api_users(self):
-        """ Test the api_users function.  """
+        """Test the api_users function."""
 
         output = self.app.get("/api/0/users")
         self.assertEqual(output.status_code, 200)
@@ -65,8 +65,8 @@ class PagureFlaskApiUSertests(tests.Modeltests):
 
     def test_api_view_user(self):
         """
-            Test the api_view_user method of the flask api
-            The tested user has no project or forks.
+        Test the api_view_user method of the flask api
+        The tested user has no project or forks.
         """
         output = self.app.get("/api/0/user/pingou")
         self.assertEqual(output.status_code, 200)
@@ -95,6 +95,7 @@ class PagureFlaskApiUSertests(tests.Modeltests):
                 "fullname": "PY C",
                 "name": "pingou",
                 "url_path": "user/pingou",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "avatar_url": "https://seccdn.libravatar.org/avatar/...",
             },
         }
@@ -108,8 +109,8 @@ class PagureFlaskApiUSertests(tests.Modeltests):
 
     def test_api_view_user_with_project(self):
         """
-            Test the api_view_user method of the flask api,
-            this time the user has some project defined.
+        Test the api_view_user method of the flask api,
+        this time the user has some project defined.
         """
         tests.create_projects(self.session)
 
@@ -164,6 +165,7 @@ class PagureFlaskApiUSertests(tests.Modeltests):
                     "description": "test project #1",
                     "fullname": "test",
                     "url_path": "test",
+                    "full_url": "http://localhost.localdomain/test",
                     "id": 1,
                     "milestones": {},
                     "name": "test",
@@ -175,6 +177,7 @@ class PagureFlaskApiUSertests(tests.Modeltests):
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                 },
                 {
@@ -201,6 +204,7 @@ class PagureFlaskApiUSertests(tests.Modeltests):
                     "date_created": "1490272832",
                     "date_modified": "1490272832",
                     "description": "test project #2",
+                    "full_url": "http://localhost.localdomain/test2",
                     "fullname": "test2",
                     "url_path": "test2",
                     "id": 2,
@@ -214,6 +218,7 @@ class PagureFlaskApiUSertests(tests.Modeltests):
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                 },
                 {
@@ -242,6 +247,7 @@ class PagureFlaskApiUSertests(tests.Modeltests):
                     "description": "namespaced test project",
                     "fullname": "somenamespace/test3",
                     "url_path": "somenamespace/test3",
+                    "full_url": "http://localhost.localdomain/somenamespace/test3",
                     "id": 3,
                     "milestones": {},
                     "name": "test3",
@@ -251,6 +257,7 @@ class PagureFlaskApiUSertests(tests.Modeltests):
                     "tags": [],
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -269,6 +276,7 @@ class PagureFlaskApiUSertests(tests.Modeltests):
                 "fullname": "PY C",
                 "name": "pingou",
                 "url_path": "user/pingou",
+                "full_url": "http://localhost.localdomain/user/pingou",
                 "avatar_url": "https://seccdn.libravatar.org/avatar/...",
             },
         }
@@ -277,8 +285,8 @@ class PagureFlaskApiUSertests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_view_user_activity_stats(self, mockemail):
-        """ Test the api_view_user_activity_stats method of the flask user
-        api. """
+        """Test the api_view_user_activity_stats method of the flask user
+        api."""
         mockemail.return_value = True
 
         tests.create_projects(self.session)
@@ -351,8 +359,8 @@ class PagureFlaskApiUSertests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_view_user_activity_date(self, mockemail):
-        """ Test the api_view_user_activity_date method of the flask user
-        api. """
+        """Test the api_view_user_activity_date method of the flask user
+        api."""
 
         self.test_api_view_user_activity_stats()
 
@@ -421,6 +429,7 @@ class PagureFlaskApiUSertests(tests.Modeltests):
                     "type": "created",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -435,6 +444,7 @@ class PagureFlaskApiUSertests(tests.Modeltests):
                     "type": "commented",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -449,6 +459,7 @@ class PagureFlaskApiUSertests(tests.Modeltests):
                     "type": "closed",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -463,6 +474,7 @@ class PagureFlaskApiUSertests(tests.Modeltests):
                     "type": "commented",
                     "user": {
                         "fullname": "PY C",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                         "name": "pingou",
                         "url_path": "user/pingou",
                     },
@@ -478,8 +490,8 @@ class PagureFlaskApiUSertests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_view_user_activity_date_1_activity(self, mockemail):
-        """ Test the api_view_user_activity_date method of the flask user
-        api when the user only did one action. """
+        """Test the api_view_user_activity_date method of the flask user
+        api when the user only did one action."""
 
         tests.create_projects(self.session)
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -747,12 +759,12 @@ class PagureFlaskApiUSertests(tests.Modeltests):
 
 
 class PagureFlaskApiUsertestrequests(tests.Modeltests):
-    """ Tests for the user requests endpoints """
+    """Tests for the user requests endpoints"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiUsertestrequests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -947,8 +959,8 @@ class PagureFlaskApiUsertestrequests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_view_user_requests_filed(self, mockemail):
-        """ Test the api_view_user_requests_filed method of the flask user
-        api """
+        """Test the api_view_user_requests_filed method of the flask user
+        api"""
 
         # First we test without the status parameter. It should default to `open`
         output = self.app.get("/api/0/user/pingou/requests/filed")
@@ -1119,8 +1131,8 @@ class PagureFlaskApiUsertestrequests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_view_user_requests_filed_created(self, mockemail):
-        """ Test the api_view_user_requests_filed method of the flask user
-        api with the created parameter """
+        """Test the api_view_user_requests_filed method of the flask user
+        api with the created parameter"""
 
         today = datetime.datetime.utcnow().date()
         output = self.app.get(
@@ -1192,8 +1204,8 @@ class PagureFlaskApiUsertestrequests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_view_user_requests_filed_updated(self, mockemail):
-        """ Test the api_view_user_requests_filed method of the flask user
-        api with the created parameter """
+        """Test the api_view_user_requests_filed method of the flask user
+        api with the created parameter"""
 
         today = datetime.datetime.utcnow().date()
         output = self.app.get(
@@ -1224,8 +1236,8 @@ class PagureFlaskApiUsertestrequests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_view_user_requests_filed_closed(self, mockemail):
-        """ Test the api_view_user_requests_filed method of the flask user
-        api with the created parameter """
+        """Test the api_view_user_requests_filed method of the flask user
+        api with the created parameter"""
 
         today = datetime.datetime.utcnow().date()
         output = self.app.get(
@@ -1256,8 +1268,8 @@ class PagureFlaskApiUsertestrequests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_view_user_requests_filed_foo(self, mockemail):
-        """ Test the api_view_user_requests_filed method of the flask user
-        api """
+        """Test the api_view_user_requests_filed method of the flask user
+        api"""
 
         # Default data returned
         output = self.app.get(
@@ -1276,7 +1288,7 @@ class PagureFlaskApiUsertestrequests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_view_user_requests_filed_foo_grp_access(self, mockemail):
-        """ Test when the user has accessed to some PRs via a group. """
+        """Test when the user has accessed to some PRs via a group."""
 
         # Add the user to a group
         msg = pagure.lib.query.add_group(
@@ -1327,8 +1339,8 @@ class PagureFlaskApiUsertestrequests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_view_user_requests_actionable(self, mockemail):
-        """ Test the api_view_user_requests_actionable method of the flask user
-        api """
+        """Test the api_view_user_requests_actionable method of the flask user
+        api"""
 
         # First we test without the status parameter. It should default to `open`
         output = self.app.get("/api/0/user/pingou/requests/actionable")
@@ -1503,8 +1515,8 @@ class PagureFlaskApiUsertestrequests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_view_user_requests_actionable_created(self, mockemail):
-        """ Test the api_view_user_requests_filed method of the flask user
-        api with the created parameter """
+        """Test the api_view_user_requests_filed method of the flask user
+        api with the created parameter"""
 
         today = datetime.datetime.utcnow().date()
         output = self.app.get(
@@ -1576,8 +1588,8 @@ class PagureFlaskApiUsertestrequests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_view_user_requests_actionable_updated(self, mockemail):
-        """ Test the api_view_user_requests_filed method of the flask user
-        api with the created parameter """
+        """Test the api_view_user_requests_filed method of the flask user
+        api with the created parameter"""
 
         today = datetime.datetime.utcnow().date()
         output = self.app.get(
@@ -1608,8 +1620,8 @@ class PagureFlaskApiUsertestrequests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_api_view_user_requests_actionable_closed(self, mockemail):
-        """ Test the api_view_user_requests_filed method of the flask user
-        api with the created parameter """
+        """Test the api_view_user_requests_filed method of the flask user
+        api with the created parameter"""
 
         today = datetime.datetime.utcnow().date()
         output = self.app.get(
@@ -1640,12 +1652,12 @@ class PagureFlaskApiUsertestrequests(tests.Modeltests):
 
 
 class PagureFlaskApiUsertestissues(tests.Modeltests):
-    """ Tests for the user issues endpoints """
+    """Tests for the user issues endpoints"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskApiUsertestissues, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -1666,7 +1678,7 @@ class PagureFlaskApiUsertestissues(tests.Modeltests):
         self.assertEqual(msg.title, "Test issue")
 
     def test_user_issues_empty(self):
-        """ Return the list of issues associated with the specified user. """
+        """Return the list of issues associated with the specified user."""
 
         output = self.app.get("/api/0/user/foo/issues")
         self.assertEqual(output.status_code, 200)
@@ -1721,7 +1733,7 @@ class PagureFlaskApiUsertestissues(tests.Modeltests):
         )
 
     def test_user_issues(self):
-        """ Return the list of issues associated with the specified user. """
+        """Return the list of issues associated with the specified user."""
 
         output = self.app.get("/api/0/user/pingou/issues")
         self.assertEqual(output.status_code, 200)
@@ -1768,6 +1780,7 @@ class PagureFlaskApiUsertestissues(tests.Modeltests):
                         "content": "We should work on this",
                         "custom_fields": [],
                         "date_created": "1513111778",
+                        "full_url": "http://localhost.localdomain/test/issue/1",
                         "depends": [],
                         "id": 1,
                         "last_updated": "1513111778",
@@ -1798,6 +1811,7 @@ class PagureFlaskApiUsertestissues(tests.Modeltests):
                             "date_created": "1513111778",
                             "date_modified": "1513111778",
                             "description": "test project #1",
+                            "full_url": "http://localhost.localdomain/test",
                             "fullname": "test",
                             "id": 1,
                             "milestones": {},
@@ -1809,6 +1823,7 @@ class PagureFlaskApiUsertestissues(tests.Modeltests):
                             "url_path": "test",
                             "user": {
                                 "fullname": "PY C",
+                                "full_url": "http://localhost.localdomain/user/pingou",
                                 "name": "pingou",
                                 "url_path": "user/pingou",
                             },
@@ -1819,6 +1834,7 @@ class PagureFlaskApiUsertestissues(tests.Modeltests):
                         "title": "Test issue",
                         "user": {
                             "fullname": "PY C",
+                            "full_url": "http://localhost.localdomain/user/pingou",
                             "name": "pingou",
                             "url_path": "user/pingou",
                         },
@@ -1850,8 +1866,8 @@ class PagureFlaskApiUsertestissues(tests.Modeltests):
         )
 
     def test_user_issues_created(self):
-        """ Return the list of issues associated with the specified user
-        and play with the created filter. """
+        """Return the list of issues associated with the specified user
+        and play with the created filter."""
 
         today = datetime.datetime.utcnow().date()
         output = self.app.get(
diff --git a/tests/test_pagure_flask_docs.py b/tests/test_pagure_flask_docs.py
index 4baa735..0752d3d 100644
--- a/tests/test_pagure_flask_docs.py
+++ b/tests/test_pagure_flask_docs.py
@@ -31,10 +31,10 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureFlaskDocstests(tests.SimplePagureTest):
-    """ Tests for flask docs of pagure """
+    """Tests for flask docs of pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskDocstests, self).setUp()
 
         pagure.docs_server.APP.config["TESTING"] = True
@@ -98,13 +98,13 @@ class PagureFlaskDocstests(tests.SimplePagureTest):
         self.session.commit()
 
     def test_view_docs_no_project(self):
-        """ Test the view_docs endpoint with no project. """
+        """Test the view_docs endpoint with no project."""
 
         output = self.app.get("/foo/docs")
         self.assertEqual(output.status_code, 404)
 
     def test_view_docs_project_no_git(self):
-        """ Test the view_docs endpoint with a project that has no
+        """Test the view_docs endpoint with a project that has no
         corresponding git repo.
         """
         tests.create_projects(self.session)
@@ -128,7 +128,7 @@ class PagureFlaskDocstests(tests.SimplePagureTest):
         )
 
     def test_view_docs_project_no_docs(self):
-        """ Test the view_docs endpoint with a project that disabled the
+        """Test the view_docs endpoint with a project that disabled the
         docs.
         """
         tests.create_projects(self.session)
@@ -146,7 +146,7 @@ class PagureFlaskDocstests(tests.SimplePagureTest):
         self.assertEqual(output.status_code, 404)
 
     def test_view_docs_empty_repo(self):
-        """ Test the view_docs endpoint when the git repo is empty. """
+        """Test the view_docs endpoint when the git repo is empty."""
         tests.create_projects(self.session)
         repo = pygit2.init_repository(
             os.path.join(self.path, "repos", "docs", "test.git"), bare=True
@@ -169,7 +169,7 @@ class PagureFlaskDocstests(tests.SimplePagureTest):
         )
 
     def test_view_docs(self):
-        """ Test the view_docs endpoint. """
+        """Test the view_docs endpoint."""
         tests.create_projects(self.session)
         repo = pygit2.init_repository(
             os.path.join(self.path, "repos", "docs", "test.git"), bare=True
@@ -220,7 +220,7 @@ class PagureFlaskDocstests(tests.SimplePagureTest):
         mock.MagicMock(side_effect=pagure.exceptions.PagureEncodingException),
     )
     def test_view_docs_encoding_error(self):
-        """ Test viewing a file of which we cannot find the encoding. """
+        """Test viewing a file of which we cannot find the encoding."""
         tests.create_projects(self.session)
         repo = pygit2.init_repository(
             os.path.join(self.path, "repos", "docs", "test.git"), bare=True
@@ -246,7 +246,7 @@ class PagureFlaskDocstests(tests.SimplePagureTest):
         "pagure.lib.encoding_utils.decode", mock.MagicMock(side_effect=IOError)
     )
     def test_view_docs_unknown_error(self):
-        """ Test viewing a file of which we cannot find the encoding. """
+        """Test viewing a file of which we cannot find the encoding."""
         tests.create_projects(self.session)
         repo = pygit2.init_repository(
             os.path.join(self.path, "repos", "docs", "test.git"), bare=True
diff --git a/tests/test_pagure_flask_dump_load_ticket.py b/tests/test_pagure_flask_dump_load_ticket.py
index aefd8a8..828a668 100644
--- a/tests/test_pagure_flask_dump_load_ticket.py
+++ b/tests/test_pagure_flask_dump_load_ticket.py
@@ -32,14 +32,14 @@ from pagure.config import config as pagure_config, reload_config
 
 
 class PagureFlaskDumpLoadTicketTests(tests.Modeltests):
-    """ Tests for flask application for dumping and re-loading the JSON of
+    """Tests for flask application for dumping and re-loading the JSON of
     a ticket.
     """
 
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.lib.git._maybe_wait")
     def test_dumping_reloading_ticket(self, mw, send_email):
-        """ Test dumping a ticket into a JSON blob. """
+        """Test dumping a ticket into a JSON blob."""
         mw.side_effect = lambda result: result.get()
         send_email.return_value = True
 
@@ -142,7 +142,7 @@ class PagureFlaskDumpLoadTicketTests(tests.Modeltests):
                 )
             ]
         )
-        self.assertIn(cnt, (9, 10))
+        self.assertIn(cnt, (9, 10, 11))
 
         last_commit = repo.revparse_single("HEAD")
         patch = pagure.lib.git.commit_to_patch(repo, last_commit)
@@ -179,7 +179,8 @@ class PagureFlaskDumpLoadTicketTests(tests.Modeltests):
         os.unlink(os.path.join(self.dbfolder, "db.sqlite"))
 
         self.db_session = pagure.lib.model.create_tables(
-            self.dbpath, acls=pagure_config.get("ACLS", {}),
+            self.dbpath,
+            acls=pagure_config.get("ACLS", {}),
         )
         self._prepare_db()
         tests.create_projects(self.session)
diff --git a/tests/test_pagure_flask_form.py b/tests/test_pagure_flask_form.py
index b32648d..3613554 100644
--- a/tests/test_pagure_flask_form.py
+++ b/tests/test_pagure_flask_form.py
@@ -29,7 +29,7 @@ import tests
 
 
 class PagureFlaskFormTests(tests.SimplePagureTest):
-    """ Tests for forms of the flask application """
+    """Tests for forms of the flask application"""
 
     @patch.dict(
         "pagure.config.config", {"SERVER_NAME": "localhost.localdomain"}
@@ -38,14 +38,14 @@ class PagureFlaskFormTests(tests.SimplePagureTest):
         super(PagureFlaskFormTests, self).setUp()
 
     def test_csrf_form_no_input(self):
-        """ Test the CSRF validation if not CSRF is specified. """
+        """Test the CSRF validation if not CSRF is specified."""
         with self.app.application.test_request_context(method="POST"):
             flask.g.session = MagicMock()
             form = pagure.forms.ConfirmationForm()
             self.assertFalse(form.validate_on_submit())
 
     def test_csrf_form_w_invalid_input(self):
-        """ Test the CSRF validation with an invalid CSRF specified. """
+        """Test the CSRF validation with an invalid CSRF specified."""
         with self.app.application.test_request_context(method="POST"):
             flask.g.session = MagicMock()
             form = pagure.forms.ConfirmationForm()
@@ -53,7 +53,7 @@ class PagureFlaskFormTests(tests.SimplePagureTest):
             self.assertFalse(form.validate_on_submit())
 
     def test_csrf_form_w_input(self):
-        """ Test the CSRF validation with a valid CSRF specified. """
+        """Test the CSRF validation with a valid CSRF specified."""
         with self.app.application.test_request_context(method="POST"):
             flask.g.session = MagicMock()
             form = pagure.forms.ConfirmationForm()
@@ -61,7 +61,7 @@ class PagureFlaskFormTests(tests.SimplePagureTest):
             self.assertTrue(form.validate_on_submit())
 
     def test_csrf_form_w_expired_input(self):
-        """ Test the CSRF validation with an expired CSRF specified. """
+        """Test the CSRF validation with an expired CSRF specified."""
         with self.app.application.test_request_context(method="POST"):
             flask.g.session = MagicMock()
             form = pagure.forms.ConfirmationForm()
@@ -101,7 +101,7 @@ class PagureFlaskFormTests(tests.SimplePagureTest):
             self.assertFalse(form.validate_on_submit())
 
     def test_csrf_form_w_unexpiring_input(self):
-        """ Test the CSRF validation with a CSRF not expiring. """
+        """Test the CSRF validation with a CSRF not expiring."""
         pagure.config.config["WTF_CSRF_TIME_LIMIT"] = None
         with self.app.application.test_request_context(method="POST"):
             flask.g.session = MagicMock()
@@ -119,7 +119,7 @@ class PagureFlaskFormTests(tests.SimplePagureTest):
             self.assertTrue(form.validate_on_submit())
 
     def test_add_user_form(self):
-        """ Test the AddUserForm of pagure.forms """
+        """Test the AddUserForm of pagure.forms"""
         with self.app.application.test_request_context(method="POST"):
             flask.g.session = MagicMock()
             form = pagure.forms.AddUserForm()
@@ -133,7 +133,7 @@ class PagureFlaskFormTests(tests.SimplePagureTest):
             self.assertTrue(form.validate_on_submit())
 
     def test_add_user_to_group_form(self):
-        """ Test the AddUserToGroup form of pagure.forms """
+        """Test the AddUserToGroup form of pagure.forms"""
         with self.app.application.test_request_context(method="POST"):
             flask.g.session = MagicMock()
             form = pagure.forms.AddUserToGroupForm()
@@ -145,7 +145,7 @@ class PagureFlaskFormTests(tests.SimplePagureTest):
             self.assertTrue(form.validate_on_submit())
 
     def test_add_group_form(self):
-        """ Test the AddGroupForm form of pagure.forms """
+        """Test the AddGroupForm form of pagure.forms"""
         with self.app.application.test_request_context(method="POST"):
             flask.g.session = MagicMock()
             form = pagure.forms.AddGroupForm()
diff --git a/tests/test_pagure_flask_internal.py b/tests/test_pagure_flask_internal.py
index 96b3c26..eae24a5 100644
--- a/tests/test_pagure_flask_internal.py
+++ b/tests/test_pagure_flask_internal.py
@@ -31,12 +31,12 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureFlaskInternaltests(tests.Modeltests):
-    """ Tests for flask Internal controller of pagure """
+    """Tests for flask Internal controller of pagure"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskInternaltests, self).setUp()
 
         pagure.config.config["IP_ALLOWED_INTERNAL"] = list(
@@ -75,7 +75,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_pull_request_add_comment(self, send_email):
-        """ Test the pull_request_add_comment function.  """
+        """Test the pull_request_add_comment function."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -150,7 +150,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_ticket_add_comment(self, send_email):
-        """ Test the ticket_add_comment function.  """
+        """Test the ticket_add_comment function."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -221,7 +221,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_private_ticket_add_comment(self, send_email):
-        """ Test the ticket_add_comment function on a private ticket.  """
+        """Test the ticket_add_comment function on a private ticket."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -298,7 +298,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_private_ticket_add_comment_acl(self, send_email):
-        """ Test the ticket_add_comment function on a private ticket.  """
+        """Test the ticket_add_comment function on a private ticket."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -421,7 +421,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_mergeable_request_pull_FF(self, send_email):
-        """ Test the mergeable_request_pull endpoint with a fast-forward
+        """Test the mergeable_request_pull endpoint with a fast-forward
         merge.
         """
         send_email.return_value = True
@@ -548,7 +548,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_mergeable_request_pull_no_change(self, send_email):
-        """ Test the mergeable_request_pull endpoint when there are no
+        """Test the mergeable_request_pull endpoint when there are no
         changes to merge.
         """
         send_email.return_value = True
@@ -666,7 +666,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_mergeable_request_pull_merge(self, send_email):
-        """ Test the mergeable_request_pull endpoint when the changes can
+        """Test the mergeable_request_pull endpoint when the changes can
         be merged with a merge commit.
         """
         send_email.return_value = True
@@ -832,7 +832,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_mergeable_request_pull_conflicts(self, send_email):
-        """ Test the mergeable_request_pull endpoint when the changes cannot
+        """Test the mergeable_request_pull endpoint when the changes cannot
         be merged due to conflicts.
         """
         send_email.return_value = True
@@ -980,7 +980,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_mergeable_request_pull_merge_no_nonff_merges(self, send_email):
-        """ Test the mergeable_request_pull endpoint when the changes can
+        """Test the mergeable_request_pull endpoint when the changes can
         be merged with a merge commit, but project settings prohibit this.
         """
         send_email.return_value = True
@@ -1139,7 +1139,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_mergeable_request_pull_minimum_score(self, send_email):
-        """ Test the mergeable_request_pull endpoint when the changes can
+        """Test the mergeable_request_pull endpoint when the changes can
         be merged with a merge FF, but project settings enforces vote on
         the PR.
         """
@@ -1284,7 +1284,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         MagicMock(side_effect=pagure.exceptions.PagureException("error")),
     )
     def test_mergeable_request_pull_merge_pagureerror(self):
-        """ Test the mergeable_request_pull endpoint when the backend
+        """Test the mergeable_request_pull endpoint when the backend
         raises an GitError exception.
         """
         # Create a git repo to play with
@@ -1419,7 +1419,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         MagicMock(side_effect=pygit2.GitError("git error")),
     )
     def test_mergeable_request_pull_merge_giterror(self):
-        """ Test the mergeable_request_pull endpoint when the backend
+        """Test the mergeable_request_pull endpoint when the backend
         raises an GitError exception.
         """
         # Create a git repo to play with
@@ -1549,7 +1549,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
             self.assertDictEqual(js_data, exp)
 
     def test_get_branches_of_commit(self):
-        """ Test the get_branches_of_commit from the internal API. """
+        """Test the get_branches_of_commit from the internal API."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
 
@@ -1720,7 +1720,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         )
 
     def test_get_branches_of_commit_with_unrelated_branches(self):
-        """ Test the get_branches_of_commit from the internal API. """
+        """Test the get_branches_of_commit from the internal API."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
 
@@ -1791,7 +1791,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         self.assertDictEqual(js_data, {"code": "OK", "branches": ["feature"]})
 
     def test_get_branches_head(self):
-        """ Test the get_branches_head from the internal API. """
+        """Test the get_branches_head from the internal API."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
 
@@ -1942,7 +1942,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         self.assertEqual(len(js_data["branches"]), 3)
 
     def test_get_stats_commits_no_token(self):
-        """ Test the get_stats_commits from the internal API. """
+        """Test the get_stats_commits from the internal API."""
         # No CSRF token
         data = {"repo": "fakerepo"}
         output = self.app.post("/pv/stats/commits/authors", data=data)
@@ -1953,7 +1953,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         )
 
     def test_get_stats_commits_invalid_repo(self):
-        """ Test the get_stats_commits from the internal API. """
+        """Test the get_stats_commits from the internal API."""
         user = tests.FakeUser()
         user.username = "pingou"
         with tests.user_set(self.app.application, user):
@@ -1973,7 +1973,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         )
 
     def test_get_stats_commits_empty_git(self):
-        """ Test the get_stats_commits from the internal API. """
+        """Test the get_stats_commits from the internal API."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
 
@@ -2005,7 +2005,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         )
 
     def test_get_stats_commits_git_populated(self):
-        """ Test the get_stats_commits from the internal API. """
+        """Test the get_stats_commits from the internal API."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
         tests.add_content_git_repo(
@@ -2084,7 +2084,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         )
 
     def test_get_stats_commits_trend_no_token(self):
-        """ Test the get_stats_commits_trend from the internal API. """
+        """Test the get_stats_commits_trend from the internal API."""
         # No CSRF token
         data = {"repo": "fakerepo"}
         output = self.app.post("/pv/stats/commits/trend", data=data)
@@ -2095,7 +2095,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         )
 
     def test_get_stats_commits_trend_invalid_repo(self):
-        """ Test the get_stats_commits_trend from the internal API. """
+        """Test the get_stats_commits_trend from the internal API."""
         user = tests.FakeUser()
         user.username = "pingou"
         with tests.user_set(self.app.application, user):
@@ -2115,7 +2115,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         )
 
     def test_get_stats_commits_trend_empty_git(self):
-        """ Test the get_stats_commits_trend from the internal API. """
+        """Test the get_stats_commits_trend from the internal API."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
 
@@ -2147,7 +2147,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         )
 
     def test_get_stats_commits_trend_git_populated(self):
-        """ Test the get_stats_commits_trend from the internal API. """
+        """Test the get_stats_commits_trend from the internal API."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
         tests.add_content_git_repo(
@@ -2177,12 +2177,12 @@ class PagureFlaskInternaltests(tests.Modeltests):
         self.assertDictEqual(js_data2, {"results": [[str(today), 2]]})
 
     def test_get_project_family_no_project(self):
-        """ Test the get_project_family from the internal API. """
+        """Test the get_project_family from the internal API."""
         output = self.app.post("/pv/test/family")
         self.assertEqual(output.status_code, 404)
 
     def test_get_project_family_no_csrf(self):
-        """ Test the get_project_family from the internal API. """
+        """Test the get_project_family from the internal API."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
         tests.add_content_git_repo(
@@ -2197,7 +2197,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         self.assertEqual(js_data["message"], "Invalid input submitted")
 
     def test_get_project_family(self):
-        """ Test the get_project_family from the internal API. """
+        """Test the get_project_family from the internal API."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
         tests.add_content_git_repo(
@@ -2218,7 +2218,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         self.assertEqual(js_data["family"], ["test"])
 
     def test_get_project_larger_family(self):
-        """ Test the get_project_family from the internal API. """
+        """Test the get_project_family from the internal API."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -2284,7 +2284,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         )
 
     def test_get_project_larger_family_pr_only(self):
-        """ Test the get_project_family from the internal API. """
+        """Test the get_project_family from the internal API."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -2750,7 +2750,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         )
 
     def test_task_info_task_running(self):
-        """ Test the task_info internal API endpoint when the task isn't
+        """Test the task_info internal API endpoint when the task isn't
         ready.
         """
         task = MagicMock()
@@ -2763,8 +2763,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
             self.assertEqual(output.status_code, 418)
 
     def test_task_info_task_passed(self):
-        """ Test the task_info internal API endpoint when the task failed.
-        """
+        """Test the task_info internal API endpoint when the task failed."""
         task = MagicMock()
         task.get = MagicMock(return_value="PASSED")
         with patch(
@@ -2776,8 +2775,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
             self.assertEqual(js_data, {"results": "PASSED"})
 
     def test_task_info_task_failed(self):
-        """ Test the task_info internal API endpoint when the task failed.
-        """
+        """Test the task_info internal API endpoint when the task failed."""
         task = MagicMock()
         task.get = MagicMock(return_value=Exception("Random error"))
         with patch(
@@ -2789,7 +2787,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
             self.assertEqual(js_data, {"results": "Random error"})
 
     def test_lookup_ssh_key(self):
-        """ Test the mergeable_request_pull endpoint when the backend
+        """Test the mergeable_request_pull endpoint when the backend
         raises an GitError exception.
         """
         tests.create_projects(self.session)
@@ -2860,7 +2858,7 @@ class PagureFlaskInternaltests(tests.Modeltests):
         {"REPOSPANNER_REGIONS": {"region": {"repo_prefix": "prefix"}}},
     )
     def test_check_ssh_access(self):
-        """ Test the SSH access check endpoint. """
+        """Test the SSH access check endpoint."""
         tests.create_projects(self.session)
         self.session.query(pagure.lib.model.Project).filter(
             pagure.lib.model.Project.name == "test2"
diff --git a/tests/test_pagure_flask_rebase.py b/tests/test_pagure_flask_rebase.py
index 984c44f..307ddb5 100644
--- a/tests/test_pagure_flask_rebase.py
+++ b/tests/test_pagure_flask_rebase.py
@@ -17,7 +17,10 @@ import sys
 import os
 
 import json
-from mock import patch, MagicMock
+import pagure_messages
+import pygit2
+from fedora_messaging import api, testing
+from mock import ANY, patch, MagicMock
 
 sys.path.insert(
     0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
@@ -28,15 +31,16 @@ import pagure.lib.tasks
 import tests
 
 
-class PagureRebasetests(tests.Modeltests):
-    """ Tests rebasing pull-request in pagure """
+class PagureRebaseBasetests(tests.Modeltests):
+    """Tests rebasing pull-request in pagure"""
 
     maxDiff = None
+    config_values = {"authbackend": "pagure"}
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
-        super(PagureRebasetests, self).setUp()
+        """Set up the environnment, ran before every tests."""
+        super(PagureRebaseBasetests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
         tests.create_projects(self.session)
@@ -99,8 +103,12 @@ class PagureRebasetests(tests.Modeltests):
         self.assertEqual(len(project.requests), 1)
         self.request = self.project.requests[0]
 
+
+class PagureRebasetests(PagureRebaseBasetests):
+    """Tests rebasing pull-request in pagure"""
+
     def test_merge_status_merge(self):
-        """ Test that the PR can be merged with a merge commit. """
+        """Test that the PR can be merged with a merge commit."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -122,8 +130,8 @@ class PagureRebasetests(tests.Modeltests):
             )
 
     def test_merge_status_needsrebase(self):
-        """ Test that the PR is marked as needing a rebase if the project
-        disables non-fast-forward merges. """
+        """Test that the PR is marked as needing a rebase if the project
+        disables non-fast-forward merges."""
         self.project = pagure.lib.query.get_authorized_project(
             self.session, "test"
         )
@@ -153,7 +161,7 @@ class PagureRebasetests(tests.Modeltests):
             )
 
     def test_rebase_task(self):
-        """ Test the rebase PR task and its outcome. """
+        """Test the rebase PR task and its outcome."""
         pagure.lib.tasks.rebase_pull_request(
             "test",
             namespace=None,
@@ -181,59 +189,9 @@ class PagureRebasetests(tests.Modeltests):
                 },
             )
 
-    def test_rebase_api_ui_logged_in(self):
-        """ Test the rebase PR API endpoint when logged in from the UI and
-        its outcome. """
-
-        user = tests.FakeUser(username="pingou")
-        with tests.user_set(self.app.application, user):
-            # Get the merge status first so it's cached and can be refreshed
-            csrf_token = self.get_csrf()
-            data = {"requestid": self.request.uid, "csrf_token": csrf_token}
-            output = self.app.post("/pv/pull-request/merge", data=data)
-            self.assertEqual(output.status_code, 200)
-            data = json.loads(output.get_data(as_text=True))
-            self.assertEqual(
-                data,
-                {
-                    "code": "MERGE",
-                    "message": "The pull-request can be merged with "
-                    "a merge commit",
-                    "short_code": "With merge",
-                },
-            )
-
-            output = self.app.post("/api/0/test/pull-request/1/rebase")
-            self.assertEqual(output.status_code, 200)
-            data = json.loads(output.get_data(as_text=True))
-            self.assertEqual(data, {"message": "Pull-request rebased"})
-
-            data = {"requestid": self.request.uid, "csrf_token": csrf_token}
-            output = self.app.post("/pv/pull-request/merge", data=data)
-            self.assertEqual(output.status_code, 200)
-            data = json.loads(output.get_data(as_text=True))
-            self.assertEqual(
-                data,
-                {
-                    "code": "FFORWARD",
-                    "message": "The pull-request can be merged and "
-                    "fast-forwarded",
-                    "short_code": "Ok",
-                },
-            )
-
-            output = self.app.get("/test/pull-request/1")
-            self.assertEqual(output.status_code, 200)
-            output_text = output.get_data(as_text=True)
-            self.assertIn("rebased onto", output_text)
-            repo = pagure.lib.query._get_project(self.session, "test")
-            # This should be pingou, but we have some bug that adds the
-            # rebase message as PR author instead of rebaser
-            self.assertEqual(repo.requests[0].comments[0].user.username, "foo")
-
     def test_rebase_api_ui_logged_in_different_user(self):
-        """ Test the rebase PR API endpoint when logged in from the UI and
-        its outcome. """
+        """Test the rebase PR API endpoint when logged in from the UI and
+        its outcome."""
         # Add 'bar' to the project 'test' so 'bar' can rebase the PR
         item = pagure.lib.model.User(
             user="bar",
@@ -244,7 +202,6 @@ class PagureRebasetests(tests.Modeltests):
         self.session.add(item)
         item = pagure.lib.model.UserEmail(user_id=2, email="bar@foo.com")
         self.session.add(item)
-
         self.session.commit()
         repo = pagure.lib.query._get_project(self.session, "test")
         msg = pagure.lib.query.add_user_to_project(
@@ -293,15 +250,18 @@ class PagureRebasetests(tests.Modeltests):
             output = self.app.get("/test/pull-request/1")
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
-            self.assertIn("rebased onto", output_text)
+            orig_repo_obj = pygit2.Repository(
+                os.path.join(self.path, "repos", "test.git")
+            )
+            orig_commit = orig_repo_obj.lookup_branch("master").peel().hex
+            expected = f'rebased onto <a href="/test/c/{orig_commit}"'
+            self.assertIn(expected, output_text)
             repo = pagure.lib.query._get_project(self.session, "test")
-            # This should be bar, but we have some bug that adds the
-            # rebase message as PR author instead of rebaser
-            self.assertEqual(repo.requests[0].comments[0].user.username, "foo")
+            self.assertEqual(repo.requests[0].comments[0].user.username, "bar")
 
     def test_rebase_api_ui_logged_in_pull_request_author(self):
-        """ Test the rebase PR API endpoint when logged in from the UI and
-        its outcome. """
+        """Test the rebase PR API endpoint when logged in from the UI and
+        its outcome."""
 
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
@@ -348,8 +308,8 @@ class PagureRebasetests(tests.Modeltests):
             self.assertEqual(repo.requests[0].comments[0].user.username, "foo")
 
     def test_rebase_api_api_logged_in(self):
-        """ Test the rebase PR API endpoint when using an API token and
-        its outcome. """
+        """Test the rebase PR API endpoint when using an API token and
+        its outcome."""
 
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -384,8 +344,8 @@ class PagureRebasetests(tests.Modeltests):
             )
 
     def test_rebase_api_conflicts(self):
-        """ Test the rebase PR API endpoint when logged in from the UI and
-        its outcome. """
+        """Test the rebase PR API endpoint when logged in from the UI and
+        its outcome."""
         tests.add_content_to_git(
             os.path.join(self.path, "repos", "test.git"),
             branch="master",
@@ -423,7 +383,7 @@ class PagureRebasetests(tests.Modeltests):
             )
 
     def test_rebase_api_api_logged_in_unknown_project(self):
-        """ Test the rebase PR API endpoint when the project doesn't exist """
+        """Test the rebase PR API endpoint when the project doesn't exist"""
 
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -440,7 +400,7 @@ class PagureRebasetests(tests.Modeltests):
         )
 
     def test_rebase_api_api_logged_in_unknown_pr(self):
-        """ Test the rebase PR API endpoint when the PR doesn't exist """
+        """Test the rebase PR API endpoint when the PR doesn't exist"""
 
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -457,7 +417,7 @@ class PagureRebasetests(tests.Modeltests):
         )
 
     def test_rebase_api_api_logged_in_unknown_token(self):
-        """ Test the rebase PR API endpoint with an invalid API token """
+        """Test the rebase PR API endpoint with an invalid API token"""
 
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -481,14 +441,404 @@ class PagureRebasetests(tests.Modeltests):
         )
 
 
+class PagureRebaseNoHooktests(PagureRebaseBasetests):
+    """Tests rebasing pull-request in pagure"""
+
+    config_values = {"authbackend": "pagure", "nogithooks": True}
+
+    @patch.dict(
+        "pagure.config.config",
+        {
+            "FEDORA_MESSAGING_NOTIFICATIONS": True,
+        },
+    )
+    def test_rebase_api_ui_logged_in(self):
+        """Test the rebase PR API endpoint when logged in from the UI and
+        its outcome."""
+
+        user = tests.FakeUser(username="pingou")
+        with tests.user_set(self.app.application, user):
+            # Get the merge status first so it's cached and can be refreshed
+            csrf_token = self.get_csrf()
+            data = {"requestid": self.request.uid, "csrf_token": csrf_token}
+            output = self.app.post("/pv/pull-request/merge", data=data)
+            self.assertEqual(output.status_code, 200)
+            data = json.loads(output.get_data(as_text=True))
+            self.assertEqual(
+                data,
+                {
+                    "code": "MERGE",
+                    "message": "The pull-request can be merged with "
+                    "a merge commit",
+                    "short_code": "With merge",
+                },
+            )
+
+            with testing.mock_sends(
+                pagure_messages.PullRequestRebasedV1(
+                    topic="pagure.pull-request.rebased",
+                    body={
+                        "pullrequest": {
+                            "id": 1,
+                            "uid": ANY,
+                            "title": "PR from the test branch",
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "test",
+                            "repo_from": {
+                                "id": 4,
+                                "name": "test",
+                                "fullname": "forks/foo/test",
+                                "url_path": "fork/foo/test",
+                                "full_url": "http://localhost.localdomain/fork/foo/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": {
+                                    "id": 1,
+                                    "name": "test",
+                                    "fullname": "test",
+                                    "url_path": "test",
+                                    "full_url": "http://localhost.localdomain/test",
+                                    "description": "test project #1",
+                                    "namespace": None,
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "date_modified": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "access_users": {
+                                        "owner": ["pingou"],
+                                        "admin": [],
+                                        "commit": [],
+                                        "collaborator": [],
+                                        "ticket": [],
+                                    },
+                                    "access_groups": {
+                                        "admin": [],
+                                        "commit": [],
+                                        "collaborator": [],
+                                        "ticket": [],
+                                    },
+                                    "tags": [],
+                                    "priorities": {},
+                                    "custom_keys": [],
+                                    "close_status": [
+                                        "Invalid",
+                                        "Insufficient data",
+                                        "Fixed",
+                                        "Duplicate",
+                                    ],
+                                    "milestones": {},
+                                },
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "foo",
+                                    "fullname": "foo bar",
+                                    "url_path": "user/foo",
+                                    "full_url": "http://localhost.localdomain/user/foo",
+                                },
+                                "access_users": {
+                                    "owner": ["foo"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "foo",
+                                "fullname": "foo bar",
+                                "url_path": "user/foo",
+                                "full_url": "http://localhost.localdomain/user/foo",
+                            },
+                            "assignee": None,
+                            "status": "Open",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [],
+                        },
+                        "agent": "pagure",
+                    },
+                ),
+                pagure_messages.PullRequestCommentAddedV1(
+                    topic="pagure.pull-request.comment.added",
+                    body={
+                        "pullrequest": {
+                            "id": 1,
+                            "uid": ANY,
+                            "title": "PR from the test branch",
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "test",
+                            "repo_from": {
+                                "id": 4,
+                                "name": "test",
+                                "fullname": "forks/foo/test",
+                                "url_path": "fork/foo/test",
+                                "full_url": "http://localhost.localdomain/fork/foo/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": {
+                                    "id": 1,
+                                    "name": "test",
+                                    "fullname": "test",
+                                    "url_path": "test",
+                                    "full_url": "http://localhost.localdomain/test",
+                                    "description": "test project #1",
+                                    "namespace": None,
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "date_modified": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "access_users": {
+                                        "owner": ["pingou"],
+                                        "admin": [],
+                                        "commit": [],
+                                        "collaborator": [],
+                                        "ticket": [],
+                                    },
+                                    "access_groups": {
+                                        "admin": [],
+                                        "commit": [],
+                                        "collaborator": [],
+                                        "ticket": [],
+                                    },
+                                    "tags": [],
+                                    "priorities": {},
+                                    "custom_keys": [],
+                                    "close_status": [
+                                        "Invalid",
+                                        "Insufficient data",
+                                        "Fixed",
+                                        "Duplicate",
+                                    ],
+                                    "milestones": {},
+                                },
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "foo",
+                                    "fullname": "foo bar",
+                                    "url_path": "user/foo",
+                                    "full_url": "http://localhost.localdomain/user/foo",
+                                },
+                                "access_users": {
+                                    "owner": ["foo"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "foo",
+                                "fullname": "foo bar",
+                                "url_path": "user/foo",
+                                "full_url": "http://localhost.localdomain/user/foo",
+                            },
+                            "assignee": None,
+                            "status": "Open",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [
+                                {
+                                    "id": 1,
+                                    "commit": None,
+                                    "tree": None,
+                                    "filename": None,
+                                    "line": None,
+                                    "comment": ANY,
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": True,
+                                    "reactions": {},
+                                }
+                            ],
+                        },
+                        "agent": "pingou",
+                    },
+                ),
+            ):
+
+                output = self.app.post("/api/0/test/pull-request/1/rebase")
+                self.assertEqual(output.status_code, 200)
+                data = json.loads(output.get_data(as_text=True))
+                self.assertEqual(data, {"message": "Pull-request rebased"})
+
+            data = {"requestid": self.request.uid, "csrf_token": csrf_token}
+            output = self.app.post("/pv/pull-request/merge", data=data)
+            self.assertEqual(output.status_code, 200)
+            data = json.loads(output.get_data(as_text=True))
+            self.assertEqual(
+                data,
+                {
+                    "code": "FFORWARD",
+                    "message": "The pull-request can be merged and "
+                    "fast-forwarded",
+                    "short_code": "Ok",
+                },
+            )
+
+            output = self.app.get("/test/pull-request/1")
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn("rebased onto", output_text)
+            repo = pagure.lib.query._get_project(self.session, "test")
+            self.assertEqual(
+                repo.requests[0].comments[0].user.username, "pingou"
+            )
+
+
 class PagureRebaseNotAllowedtests(tests.Modeltests):
-    """ Tests rebasing pull-request in pagure """
+    """Tests rebasing pull-request in pagure"""
 
     maxDiff = None
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureRebaseNotAllowedtests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -553,8 +903,8 @@ class PagureRebaseNotAllowedtests(tests.Modeltests):
         self.request = self.project.requests[0]
 
     def test_rebase_api_ui_logged_in(self):
-        """ Test the rebase PR API endpoint when logged in from the UI and
-        its outcome. """
+        """Test the rebase PR API endpoint when logged in from the UI and
+        its outcome."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -623,13 +973,13 @@ class PagureRebaseNotAllowedtests(tests.Modeltests):
             output_text = output.get_data(as_text=True)
             self.assertIn("rebased onto", output_text)
             repo = pagure.lib.query._get_project(self.session, "test")
-            # This should be pingou, but we have some bug that adds the
-            # rebase message as PR author instead of rebaser
-            self.assertEqual(repo.requests[0].comments[0].user.username, "foo")
+            self.assertEqual(
+                repo.requests[0].comments[0].user.username, "pingou"
+            )
 
     def test_rebase_api_ui_logged_in_different_user(self):
-        """ Test the rebase PR API endpoint when logged in from the UI and
-        its outcome. """
+        """Test the rebase PR API endpoint when logged in from the UI and
+        its outcome."""
         # Add 'bar' to the project 'test' so 'bar' can rebase the PR
         item = pagure.lib.model.User(
             user="bar",
@@ -713,13 +1063,11 @@ class PagureRebaseNotAllowedtests(tests.Modeltests):
             output_text = output.get_data(as_text=True)
             self.assertIn("rebased onto", output_text)
             repo = pagure.lib.query._get_project(self.session, "test")
-            # This should be bar, but we have some bug that adds the
-            # rebase message as PR author instead of rebaser
-            self.assertEqual(repo.requests[0].comments[0].user.username, "foo")
+            self.assertEqual(repo.requests[0].comments[0].user.username, "bar")
 
     def test_rebase_api_api_logged_in(self):
-        """ Test the rebase PR API endpoint when using an API token and
-        its outcome. """
+        """Test the rebase PR API endpoint when using an API token and
+        its outcome."""
 
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -778,8 +1126,8 @@ class PagureRebaseNotAllowedtests(tests.Modeltests):
             )
 
     def test_rebase_api_ui_logged_in_pull_request_author(self):
-        """ Test the rebase PR API endpoint when logged in from the UI and
-        its outcome. """
+        """Test the rebase PR API endpoint when logged in from the UI and
+        its outcome."""
 
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
diff --git a/tests/test_pagure_flask_ui_app.py b/tests/test_pagure_flask_ui_app.py
index 6bbd066..9f50fe3 100644
--- a/tests/test_pagure_flask_ui_app.py
+++ b/tests/test_pagure_flask_ui_app.py
@@ -31,10 +31,10 @@ import tests
 
 
 class PagureFlaskApptests(tests.Modeltests):
-    """ Tests for flask app controller of pagure """
+    """Tests for flask app controller of pagure"""
 
     def test_watch_list(self):
-        """ Test for watch list of a user """
+        """Test for watch list of a user"""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -55,7 +55,7 @@ class PagureFlaskApptests(tests.Modeltests):
             )
 
     def test_view_users(self):
-        """ Test the view_users endpoint. """
+        """Test the view_users endpoint."""
 
         output = self.app.get("/users/?page=abc")
         self.assertEqual(output.status_code, 200)
@@ -78,7 +78,7 @@ class PagureFlaskApptests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"ITEM_PER_PAGE": 2})
     def test_view_user_repo_cnt(self):
-        """ Test the repo counts on the view_user endpoint. """
+        """Test the repo counts on the view_user endpoint."""
         tests.create_projects(self.session)
         self.gitrepos = tests.create_projects_git(
             pagure.config.config["GIT_FOLDER"]
@@ -113,7 +113,7 @@ class PagureFlaskApptests(tests.Modeltests):
         )
 
     def test_view_user(self):
-        """ Test the view_user endpoint. """
+        """Test the view_user endpoint."""
 
         output = self.app.get("/user/pingou?repopage=abc&forkpage=def")
         self.assertEqual(output.status_code, 200)
@@ -188,7 +188,7 @@ class PagureFlaskApptests(tests.Modeltests):
         },
     )
     def test_adopt_repos(self):
-        """ Test the new_project endpoint with existing git repo. """
+        """Test the new_project endpoint with existing git repo."""
         # Before
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 0)
@@ -221,7 +221,7 @@ class PagureFlaskApptests(tests.Modeltests):
         {"PAGURE_ADMIN_USERS": [], "USERS_IGNORE_EXISTING_REPOS": ["pingou"]},
     )
     def test_adopt_repos_non_admin(self):
-        """ Test the new_project endpoint with existing git repo for non-admins. """
+        """Test the new_project endpoint with existing git repo for non-admins."""
         # Before
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 0)
@@ -254,7 +254,7 @@ class PagureFlaskApptests(tests.Modeltests):
         {"PAGURE_ADMIN_USERS": [], "USERS_IGNORE_EXISTING_REPOS": []},
     )
     def test_adopt_repos_not_allowed(self):
-        """ Test the new_project endpoint with existing git repo for no access. """
+        """Test the new_project endpoint with existing git repo for no access."""
         # Before
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 0)
@@ -284,7 +284,7 @@ class PagureFlaskApptests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PROJECT_NAME_REGEX": "^1[a-z]*$"})
     def test_new_project_diff_regex(self):
-        """ Test the new_project endpoint with a different regex. """
+        """Test the new_project endpoint with a different regex."""
         # Before
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 0)
@@ -317,7 +317,7 @@ class PagureFlaskApptests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_new_project_private(self):
-        """ Test the new_project endpoint for a private project. """
+        """Test the new_project endpoint for a private project."""
         # Before
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 0)
@@ -436,7 +436,7 @@ class PagureFlaskApptests(tests.Modeltests):
         )
 
     def test_non_ascii_new_project(self):
-        """ Test the new_project endpoint with a non-ascii project. """
+        """Test the new_project endpoint with a non-ascii project."""
         # Before
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 0)
@@ -545,8 +545,7 @@ class PagureFlaskApptests(tests.Modeltests):
 
     @patch("pygit2.init_repository", wraps=pygit2.init_repository)
     def test_new_project_with_template(self, pygit2init):
-        """ Test the new_project endpoint for a new project with a template set.
-        """
+        """Test the new_project endpoint for a new project with a template set."""
         # Before
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 0)
@@ -660,7 +659,7 @@ class PagureFlaskApptests(tests.Modeltests):
 
     @patch("pagure.ui.app.admin_session_timedout")
     def test_user_settings(self, ast):
-        """ Test the user_settings endpoint. """
+        """Test the user_settings endpoint."""
         ast.return_value = False
 
         user = tests.FakeUser()
@@ -686,7 +685,7 @@ class PagureFlaskApptests(tests.Modeltests):
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_add_user_sshkey(self, ast):
-        """ Test the add_user_sshkey endpoint. """
+        """Test the add_user_sshkey endpoint."""
         ast.return_value = False
 
         # User not logged in
@@ -787,7 +786,7 @@ class PagureFlaskApptests(tests.Modeltests):
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_remove_user_sshkey(self, ast):
-        """ Test the remove_sshkey endpoint. """
+        """Test the remove_sshkey endpoint."""
         ast.return_value = False
 
         user = tests.FakeUser()
@@ -848,7 +847,7 @@ class PagureFlaskApptests(tests.Modeltests):
             self.assertIn("SSH key removed", output_text)
 
     def patched_commit_exists(user, namespace, repo, githash):
-        """ Patched version of pagure.pfmarkdown._commit_exists to enforce
+        """Patched version of pagure.pfmarkdown._commit_exists to enforce
         returning true on some given hash without having us actually check
         the git repos.
         """
@@ -866,7 +865,7 @@ class PagureFlaskApptests(tests.Modeltests):
         MagicMock(side_effect=patched_commit_exists),
     )
     def test_patched_markdown_preview(self):
-        """ Test the markdown_preview endpoint. """
+        """Test the markdown_preview endpoint."""
 
         data = {"content": "test\n----\n\n * 1\n * item 2"}
 
@@ -933,8 +932,7 @@ class PagureFlaskApptests(tests.Modeltests):
                 self.assertEqual(expected[idx], output.get_data(as_text=True))
 
     def test_markdown_preview(self):
-        """ Test the markdown_preview endpoint with a non-existing commit.
-        """
+        """Test the markdown_preview endpoint with a non-existing commit."""
 
         user = tests.FakeUser()
         user.username = "foo"
@@ -960,7 +958,7 @@ class PagureFlaskApptests(tests.Modeltests):
             self.assertEqual(exp, output.get_data(as_text=True))
 
     def test_markdown_preview_valid_commit(self):
-        """ Test the markdown_preview endpoint with an existing commit. """
+        """Test the markdown_preview endpoint with an existing commit."""
 
         user = tests.FakeUser()
         user.username = "foo"
@@ -998,7 +996,7 @@ class PagureFlaskApptests(tests.Modeltests):
 
     @patch("pagure.ui.app.admin_session_timedout")
     def test_remove_user_email(self, ast):
-        """ Test the remove_user_email endpoint. """
+        """Test the remove_user_email endpoint."""
         ast.return_value = False
 
         user = tests.FakeUser()
@@ -1098,7 +1096,7 @@ class PagureFlaskApptests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.ui.app.admin_session_timedout")
     def test_add_api_user_email(self, ast, send_email):
-        """ Test the add_api_user_email endpoint. """
+        """Test the add_api_user_email endpoint."""
         send_email.return_value = True
         ast.return_value = False
 
@@ -1233,7 +1231,7 @@ class PagureFlaskApptests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.ui.app.admin_session_timedout")
     def test_set_default_email(self, ast, send_email):
-        """ Test the set_default_email endpoint. """
+        """Test the set_default_email endpoint."""
         send_email.return_value = True
         ast.return_value = False
 
@@ -1310,7 +1308,7 @@ class PagureFlaskApptests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.ui.app.admin_session_timedout")
     def test_reconfirm_email(self, ast, send_email):
-        """ Test the reconfirm_email endpoint. """
+        """Test the reconfirm_email endpoint."""
         send_email.return_value = True
         ast.return_value = False
 
@@ -1393,7 +1391,7 @@ class PagureFlaskApptests(tests.Modeltests):
 
     @patch("pagure.ui.app.admin_session_timedout")
     def test_confirm_email(self, ast):
-        """ Test the confirm_email endpoint. """
+        """Test the confirm_email endpoint."""
         output = self.app.get("/settings/email/confirm/foobar")
         self.assertEqual(output.status_code, 302)
 
@@ -1450,7 +1448,7 @@ class PagureFlaskApptests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_view_my_requests(self):
-        """Test the view_user_requests endpoint. """
+        """Test the view_user_requests endpoint."""
         # Create the PR
         tests.create_projects(self.session)
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -1511,7 +1509,7 @@ class PagureFlaskApptests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_view_my_requests_pr_in_another_project(self):
         """Test the view_user_requests endpoint when the user opened a PR
-        in another project. """
+        in another project."""
         # Pingou creates the PR on test
         tests.create_projects(self.session)
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -1544,12 +1542,19 @@ class PagureFlaskApptests(tests.Modeltests):
         self.assertEqual(req.title, "test pull-request #2")
 
         # Check pingou's PR list
-        output = self.app.get("/user/pingou/requests")
+        output = self.app.get("/user/pingou/requests?type=filed")
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
         self.assertIn("test pull-request #1", output_text)
+        self.assertNotIn("test pull-request #2", output_text)
+        self.assertEqual(output_text.count('pr-status pr-status-open"'), 1)
+
+        output = self.app.get("/user/pingou/requests?type=actionable")
+        self.assertEqual(output.status_code, 200)
+        output_text = output.get_data(as_text=True)
+        self.assertNotIn("test pull-request #1", output_text)
         self.assertIn("test pull-request #2", output_text)
-        self.assertEqual(output_text.count('pr-status pr-status-open"'), 2)
+        self.assertEqual(output_text.count('pr-status pr-status-open"'), 1)
 
         # Check foo's PR list
         output = self.app.get("/user/foo/requests")
@@ -1563,7 +1568,7 @@ class PagureFlaskApptests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_view_my_requests_against_another_project(self):
         """Test the view_user_requests endpoint when there is a PR opened
-        by me against a project I do not have rights on. """
+        by me against a project I do not have rights on."""
         # Create the PR
         tests.create_projects(self.session)
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -1717,7 +1722,7 @@ class PagureFlaskApptests(tests.Modeltests):
 
     def test_view_my_issues_tickets_turned_off(self):
         """Test the view_user_issues endpoint when the user exists and
-        and ENABLE_TICKETS is False """
+        and ENABLE_TICKETS is False"""
 
         # Turn off the tickets instance wide
         pagure.config.config["ENABLE_TICKETS"] = False
@@ -1728,7 +1733,7 @@ class PagureFlaskApptests(tests.Modeltests):
 
     @patch("pagure.ui.app.admin_session_timedout")
     def test_add_user_token(self, ast):
-        """ Test the add_user_token endpoint. """
+        """Test the add_user_token endpoint."""
         ast.return_value = False
 
         user = tests.FakeUser()
@@ -1820,7 +1825,7 @@ class PagureFlaskApptests(tests.Modeltests):
 
     @patch("pagure.ui.app.admin_session_timedout")
     def test_revoke_api_user_token(self, ast):
-        """ Test the revoke_api_user_token endpoint. """
+        """Test the revoke_api_user_token endpoint."""
         ast.return_value = False
 
         user = tests.FakeUser()
@@ -1927,8 +1932,8 @@ class PagureFlaskApptests(tests.Modeltests):
     @patch.dict("pagure.config.config", {"PAGURE_AUTH": "fas"})
     @patch.dict("pagure.utils.pagure_config", {"PAGURE_AUTH": "fas"})
     def test_create_project_auth_FAS_no_FPCA(self):
-        """ Test creating a project when auth is FAS and the user did not
-        sign the FPCA. """
+        """Test creating a project when auth is FAS and the user did not
+        sign the FPCA."""
 
         user = tests.FakeUser(username="foo", cla_done=False)
         with tests.user_set(self.app.application, user):
@@ -1937,7 +1942,7 @@ class PagureFlaskApptests(tests.Modeltests):
             output_text = output.get_data(as_text=True)
             self.assertIn("<title>Home - Pagure</title>", output_text)
             self.assertIn(
-                '</i> You must <a href="https://admin.fedoraproject.org/accounts/'
+                '</i> You must <a href="https://accounts.fedoraproject.org/'
                 '">sign the FPCA</a> (Fedora Project Contributor Agreement) '
                 "to use pagure</div>",
                 output_text,
@@ -1945,10 +1950,10 @@ class PagureFlaskApptests(tests.Modeltests):
 
 
 class PagureFlaskAppAboutPagetests(tests.Modeltests):
-    """ Unit-tests for the about page. """
+    """Unit-tests for the about page."""
 
     def test_about_page(self):
-        """ Test the about page when an admin_email is set. """
+        """Test the about page when an admin_email is set."""
         output = self.app.get("/about/")
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
@@ -1965,7 +1970,7 @@ class PagureFlaskAppAboutPagetests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"ADMIN_EMAIL": "admin@fp.o"})
     def test_about_page_admin_email(self):
-        """ Test the about page when an admin_email is set. """
+        """Test the about page when an admin_email is set."""
         output = self.app.get("/about/")
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
@@ -1980,12 +1985,12 @@ class PagureFlaskAppAboutPagetests(tests.Modeltests):
 
 
 class PagureFlaskAppNoDocstests(tests.Modeltests):
-    """ Tests for flask app controller of pagure """
+    """Tests for flask app controller of pagure"""
 
     config_values = {"enable_docs": False, "docs_folder": None}
 
     def test_new_project_no_docs_folder(self):
-        """ Test the new_project endpoint with DOCS_FOLDER is None. """
+        """Test the new_project endpoint with DOCS_FOLDER is None."""
         # Before
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 0)
@@ -2054,12 +2059,12 @@ class PagureFlaskAppNoDocstests(tests.Modeltests):
 
 
 class PagureFlaskAppNoTicketstests(tests.Modeltests):
-    """ Tests for flask app controller of pagure """
+    """Tests for flask app controller of pagure"""
 
     config_values = {"enable_tickets": False, "tickets_folder": None}
 
     def test_new_project_no_tickets_folder(self):
-        """ Test the new_project endpoint with TICKETS_FOLDER is None. """
+        """Test the new_project endpoint with TICKETS_FOLDER is None."""
         # Before
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 0)
@@ -2130,7 +2135,7 @@ class PagureFlaskAppNoTicketstests(tests.Modeltests):
 class PagureFlaskAppRenewUserApiTokentests(tests.Modeltests):
     @patch("pagure.decorators.admin_session_timedout")
     def setUp(self, ast):
-        """ Constructor """
+        """Constructor"""
         super(PagureFlaskAppRenewUserApiTokentests, self).setUp()
 
         self.ast = ast
@@ -2166,14 +2171,14 @@ class PagureFlaskAppRenewUserApiTokentests(tests.Modeltests):
         self.token = userobj.tokens[0].id
 
     def test_renew_api_token_not_in(self):
-        """ Test the renew_api_token endpoint. """
+        """Test the renew_api_token endpoint."""
         # User not logged in
         output = self.app.post("/settings/token/renew/123")
         self.assertEqual(output.status_code, 302)
 
     @patch("pagure.ui.app.admin_session_timedout")
     def test_renew_api_token_session_old(self, ast):
-        """ Test the renew_api_token endpoint. """
+        """Test the renew_api_token endpoint."""
         ast.return_value = True
 
         user = tests.FakeUser(username="pingou")
@@ -2189,7 +2194,7 @@ class PagureFlaskAppRenewUserApiTokentests(tests.Modeltests):
             self.assertIn("Action canceled, try it again", output_text)
 
     def test_renew_api_token_invalid_token(self):
-        """ Test the renew_api_token endpoint. """
+        """Test the renew_api_token endpoint."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -2202,7 +2207,7 @@ class PagureFlaskAppRenewUserApiTokentests(tests.Modeltests):
             self.assertIn("<p>Token not found</p>", output_text)
 
     def test_renew_api_token(self):
-        """ Test the renew_api_token endpoint. """
+        """Test the renew_api_token endpoint."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -2241,10 +2246,10 @@ class PagureFlaskAppRenewUserApiTokentests(tests.Modeltests):
 
 
 class PagureFlaskAppNewProjecttests(tests.Modeltests):
-    """ Tests creating new project via the flask app controller of pagure """
+    """Tests creating new project via the flask app controller of pagure"""
 
     def setUp(self):
-        """ Setup the environment. """
+        """Setup the environment."""
         super(PagureFlaskAppNewProjecttests, self).setUp()
 
         # Before
@@ -2324,6 +2329,11 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
                 '<a href="/testproject"><strong>testproject</strong></a>',
                 output_text,
             )
+            self.assertIn(
+                '<code class="py-1 px-2 font-weight-bold '
+                'commit_branch">master</code>',
+                output_text,
+            )
 
         # After
         projects = pagure.lib.query.search_projects(self.session)
@@ -2335,8 +2345,8 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"ENABLE_UI_NEW_PROJECTS": False})
     def test_new_project_when_turned_off_in_the_ui(self):
-        """ Test the new_project endpoint when new project creation is
-        not allowed in the UI of this pagure instance. """
+        """Test the new_project endpoint when new project creation is
+        not allowed in the UI of this pagure instance."""
 
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
@@ -2350,8 +2360,8 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"ENABLE_UI_NEW_PROJECTS": False})
     def test_new_project_button_when_turned_off_in_the_ui_no_project(self):
-        """ Test the index endpoint when new project creation is
-        not allowed in the UI of this pagure instance. """
+        """Test the index endpoint when new project creation is
+        not allowed in the UI of this pagure instance."""
 
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
@@ -2374,8 +2384,8 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"ENABLE_UI_NEW_PROJECTS": False})
     def test_new_project_button_when_turned_off_in_the_ui_w_project(self):
-        """ Test the index endpoint when new project creation is
-        not allowed in the UI of this pagure instance. """
+        """Test the index endpoint when new project creation is
+        not allowed in the UI of this pagure instance."""
         tests.create_projects(self.session)
 
         user = tests.FakeUser(username="pingou")
@@ -2398,8 +2408,7 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
             )
 
     def test_new_project_with_dot(self):
-        """ Test the new_project endpoint when new project contains a dot.
-        """
+        """Test the new_project endpoint when new project contains a dot."""
         # Before
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 0)
@@ -2429,8 +2438,7 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
         self.assertEqual(len(projects), 1)
 
     def test_new_project_with_plus(self):
-        """ Test the new_project endpoint when new project contains a plus sign.
-        """
+        """Test the new_project endpoint when new project contains a plus sign."""
         # Before
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 0)
@@ -2465,8 +2473,7 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
         self.assertEqual(repo.listall_branches(), [])
 
     def test_new_project_with_default_branch(self):
-        """ Test the new_project endpoint when new project contains a plus sign.
-        """
+        """Test the new_project endpoint when new project contains a plus sign."""
         # Before
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 0)
@@ -2492,6 +2499,11 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
                 '<a href="/project_main"><strong>project_main</strong></a>',
                 output_text,
             )
+            self.assertIn(
+                '<code class="py-1 px-2 font-weight-bold '
+                'commit_branch">main</code>',
+                output_text,
+            )
 
         # After
         projects = pagure.lib.query.search_projects(self.session)
@@ -2500,9 +2512,90 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
         repo = pygit2.Repository(projects[0].repopath("main"))
         self.assertEqual(repo.listall_branches(), ["main"])
 
+    @patch.dict("pagure.config.config", {"GIT_DEFAULT_BRANCH": "main"})
+    def test_new_project_with_default_branch_instance_wide(self):
+        """Test the new_project endpoint when new project contains a plus sign."""
+        # Before
+        projects = pagure.lib.query.search_projects(self.session)
+        self.assertEqual(len(projects), 0)
+
+        user = tests.FakeUser(username="foo")
+        with tests.user_set(self.app.application, user):
+            csrf_token = self.get_csrf()
+
+            data = {
+                "description": "Project #1.",
+                "name": "project_main",
+                "csrf_token": csrf_token,
+                "create_readme": True,
+            }
+            output = self.app.post("/new/", data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                "<title>Overview - project_main - Pagure</title>", output_text
+            )
+            self.assertIn(
+                '<a href="/project_main"><strong>project_main</strong></a>',
+                output_text,
+            )
+            self.assertIn(
+                '<code class="py-1 px-2 font-weight-bold '
+                'commit_branch">main</code>',
+                output_text,
+            )
+
+        # After
+        projects = pagure.lib.query.search_projects(self.session)
+        self.assertEqual(len(projects), 1)
+
+        repo = pygit2.Repository(projects[0].repopath("main"))
+        self.assertEqual(repo.listall_branches(), ["main"])
+
+    @patch.dict("pagure.config.config", {"GIT_DEFAULT_BRANCH": "main"})
+    def test_new_project_with_default_branch_instance_wide_overriden(self):
+        """Test the new_project endpoint when new project contains a plus sign."""
+        # Before
+        projects = pagure.lib.query.search_projects(self.session)
+        self.assertEqual(len(projects), 0)
+
+        user = tests.FakeUser(username="foo")
+        with tests.user_set(self.app.application, user):
+            csrf_token = self.get_csrf()
+
+            data = {
+                "description": "Project #1.",
+                "name": "project_main",
+                "csrf_token": csrf_token,
+                "default_branch": "rawhide",
+                "create_readme": True,
+            }
+            output = self.app.post("/new/", data=data, follow_redirects=True)
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                "<title>Overview - project_main - Pagure</title>", output_text
+            )
+            self.assertIn(
+                '<a href="/project_main"><strong>project_main</strong></a>',
+                output_text,
+            )
+            self.assertIn(
+                '<code class="py-1 px-2 font-weight-bold '
+                'commit_branch">rawhide</code>',
+                output_text,
+            )
+
+        # After
+        projects = pagure.lib.query.search_projects(self.session)
+        self.assertEqual(len(projects), 1)
+
+        repo = pygit2.Repository(projects[0].repopath("main"))
+        self.assertEqual(repo.listall_branches(), ["rawhide"])
+
     def test_new_project_when_turned_off(self):
-        """ Test the new_project endpoint when new project creation is
-        not allowed in the pagure instance. """
+        """Test the new_project endpoint when new project creation is
+        not allowed in the pagure instance."""
 
         # turn the project creation off
         pagure.config.config["ENABLE_NEW_PROJECTS"] = False
@@ -2578,7 +2671,7 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
         pagure.config.config["ENABLE_NEW_PROJECTS"] = True
 
     def test_new_project_mirrored_invalid_url(self):
-        """ Test the new_project with a mirrored repo but an invalid URL. """
+        """Test the new_project with a mirrored repo but an invalid URL."""
 
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
@@ -2601,7 +2694,7 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
             self.assertIn("Invalid input.&nbsp;", output_text)
 
     def test_new_project_mirrored_invalid_sshurl(self):
-        """ Test the new_project with a mirrored repo but an invalid
+        """Test the new_project with a mirrored repo but an invalid
         SSH-like url.
         """
 
@@ -2626,7 +2719,7 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
             self.assertIn("Invalid input.&nbsp;", output_text)
 
     def test_new_project_mirrored_valid_url(self):
-        """ Test the new_project with a mirrored repo with a valid url. """
+        """Test the new_project with a mirrored repo with a valid url."""
 
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
@@ -2635,7 +2728,7 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
             output_text = output.get_data(as_text=True)
             self.assertIn(
                 '<strong><label for="mirrored_from">Mirror from URL'
-                "</label></strong>",
+                "</label> </strong>",
                 output_text,
             )
 
@@ -2662,7 +2755,7 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"DISABLE_MIRROR_IN": True})
     def test_new_project_mirrored_mirror_disabled(self):
-        """ Test the new_project with a mirrored repo when that feature is
+        """Test the new_project with a mirrored repo when that feature is
         disabled.
         """
 
@@ -2673,7 +2766,7 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
             output_text = output.get_data(as_text=True)
             self.assertNotIn(
                 '<strong><label for="mirrored_from">Mirror from URL'
-                "</label></strong>",
+                "</label> </strong>",
                 output_text,
             )
 
@@ -2697,7 +2790,7 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
             )
 
     def test_new_project(self):
-        """ Test the new_project endpoint. """
+        """Test the new_project endpoint."""
 
         user = tests.FakeUser()
         with tests.user_set(self.app.application, user):
@@ -2778,8 +2871,7 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"USER_NAMESPACE": True})
     def test_new_project_user_namespaced(self):
-        """ Test the new_project with a user namespaced enabled.
-        """
+        """Test the new_project with a user namespaced enabled."""
 
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
@@ -2838,8 +2930,7 @@ class PagureFlaskAppNewProjecttests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"USER_NAMESPACE": True})
     def test_new_project_user_namespaced_invalid_user(self):
-        """ Test the new_project with a user namespaced enabled.
-        """
+        """Test the new_project with a user namespaced enabled."""
         tests.create_user(self.session, "docs", "evil docs", ["docs@bar.com"])
 
         user = tests.FakeUser(username="docs")
diff --git a/tests/test_pagure_flask_ui_app_browse.py b/tests/test_pagure_flask_ui_app_browse.py
index 1fac545..b2026e2 100644
--- a/tests/test_pagure_flask_ui_app_browse.py
+++ b/tests/test_pagure_flask_ui_app_browse.py
@@ -25,10 +25,10 @@ import tests
 
 
 class PagureFlaskAppBrowsetests(tests.Modeltests):
-    """ Tests for the browse pages of flask app controller of pagure """
+    """Tests for the browse pages of flask app controller of pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskAppBrowsetests, self).setUp()
 
         tests.create_projects(self.session)
@@ -46,8 +46,8 @@ class PagureFlaskAppBrowsetests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_browse_project_logged_in_private_project(self):
-        """ Test the browse project endpoint when logged in with a private
-        project. """
+        """Test the browse project endpoint when logged in with a private
+        project."""
 
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
@@ -69,8 +69,8 @@ class PagureFlaskAppBrowsetests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_browse_project_unauth_private_project(self):
-        """ Test the browse project endpoint when logged out with a private
-        project. """
+        """Test the browse project endpoint when logged out with a private
+        project."""
 
         output = self.app.get("/browse/projects/")
         self.assertEqual(output.status_code, 200)
@@ -90,8 +90,8 @@ class PagureFlaskAppBrowsetests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_browse_project_logged_in_no_access_private_project(self):
-        """ Test the browse project endpoint when logged in as an user that
-        has no access to the private project. """
+        """Test the browse project endpoint when logged in as an user that
+        has no access to the private project."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -113,8 +113,8 @@ class PagureFlaskAppBrowsetests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_browse_project_logged_in_ticket_private_project(self):
-        """ Test the browse project endpoint when logged in as an user that
-        has no access to the private project. """
+        """Test the browse project endpoint when logged in as an user that
+        has no access to the private project."""
 
         # Add user 'pingou' with ticket access on repo
         repo = pagure.lib.query._get_project(self.session, "test3")
@@ -145,8 +145,8 @@ class PagureFlaskAppBrowsetests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_browse_project_logged_in_commit_private_project(self):
-        """ Test the browse project endpoint when logged in as an user that
-        has no access to the private project. """
+        """Test the browse project endpoint when logged in as an user that
+        has no access to the private project."""
 
         # Add user 'pingou' with commit access on repo
         repo = pagure.lib.query._get_project(self.session, "test3")
@@ -176,8 +176,8 @@ class PagureFlaskAppBrowsetests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_browse_project_logged_in_admin_private_project(self):
-        """ Test the browse project endpoint when logged in as an user that
-        has no access to the private project. """
+        """Test the browse project endpoint when logged in as an user that
+        has no access to the private project."""
 
         # Add user 'pingou' with admin access on repo
         repo = pagure.lib.query._get_project(self.session, "test3")
@@ -207,10 +207,10 @@ class PagureFlaskAppBrowsetests(tests.Modeltests):
 
 
 class PagureFlaskAppBrowseGroupAdmintests(tests.Modeltests):
-    """ Tests for the browse pages of flask app controller of pagure """
+    """Tests for the browse pages of flask app controller of pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskAppBrowseGroupAdmintests, self).setUp()
 
         tests.create_projects(self.session)
@@ -255,8 +255,8 @@ class PagureFlaskAppBrowseGroupAdmintests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_browse_project_user_not_in_group(self):
-        """ Test the browse project endpoint when logged in as an user that
-        has no access to the private project via a group as admin. """
+        """Test the browse project endpoint when logged in as an user that
+        has no access to the private project via a group as admin."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -278,8 +278,8 @@ class PagureFlaskAppBrowseGroupAdmintests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_browse_project_user_in_group(self):
-        """ Test the browse project endpoint when logged in as an user that
-        has no access to the private project via a group as admin. """
+        """Test the browse project endpoint when logged in as an user that
+        has no access to the private project via a group as admin."""
         group = pagure.lib.query.search_groups(self.session, group_name="JL")
 
         pagure.lib.query.add_user_to_group(
@@ -311,10 +311,10 @@ class PagureFlaskAppBrowseGroupAdmintests(tests.Modeltests):
 
 
 class PagureFlaskAppBrowseGroupCommittests(tests.Modeltests):
-    """ Tests for the browse pages of flask app controller of pagure """
+    """Tests for the browse pages of flask app controller of pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskAppBrowseGroupCommittests, self).setUp()
 
         tests.create_projects(self.session)
@@ -359,8 +359,8 @@ class PagureFlaskAppBrowseGroupCommittests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_browse_project_user_not_in_group(self):
-        """ Test the browse project endpoint when logged in as an user that
-        has no access to the private project via a group as admin. """
+        """Test the browse project endpoint when logged in as an user that
+        has no access to the private project via a group as admin."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -382,8 +382,8 @@ class PagureFlaskAppBrowseGroupCommittests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_browse_project_user_in_group(self):
-        """ Test the browse project endpoint when logged in as an user that
-        has no access to the private project via a group as admin. """
+        """Test the browse project endpoint when logged in as an user that
+        has no access to the private project via a group as admin."""
         group = pagure.lib.query.search_groups(self.session, group_name="JL")
 
         pagure.lib.query.add_user_to_group(
@@ -415,10 +415,10 @@ class PagureFlaskAppBrowseGroupCommittests(tests.Modeltests):
 
 
 class PagureFlaskAppBrowseGroupTickettests(tests.Modeltests):
-    """ Tests for the browse pages of flask app controller of pagure """
+    """Tests for the browse pages of flask app controller of pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskAppBrowseGroupTickettests, self).setUp()
 
         tests.create_projects(self.session)
@@ -463,8 +463,8 @@ class PagureFlaskAppBrowseGroupTickettests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_browse_project_user_not_in_group(self):
-        """ Test the browse project endpoint when logged in as an user that
-        has no access to the private project via a group as admin. """
+        """Test the browse project endpoint when logged in as an user that
+        has no access to the private project via a group as admin."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -486,8 +486,8 @@ class PagureFlaskAppBrowseGroupTickettests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_browse_project_user_in_group(self):
-        """ Test the browse project endpoint when logged in as an user that
-        has no access to the private project via a group as admin. """
+        """Test the browse project endpoint when logged in as an user that
+        has no access to the private project via a group as admin."""
         group = pagure.lib.query.search_groups(self.session, group_name="JL")
 
         pagure.lib.query.add_user_to_group(
diff --git a/tests/test_pagure_flask_ui_app_give_project.py b/tests/test_pagure_flask_ui_app_give_project.py
index 705599c..4bbdb4c 100644
--- a/tests/test_pagure_flask_ui_app_give_project.py
+++ b/tests/test_pagure_flask_ui_app_give_project.py
@@ -28,10 +28,10 @@ import tests
 
 
 class PagureFlaskGiveRepotests(tests.SimplePagureTest):
-    """ Tests for give a project on pagure """
+    """Tests for give a project on pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskGiveRepotests, self).setUp()
 
         pagure.config.config["VIRUS_SCAN_ATTACHMENTS"] = False
@@ -52,14 +52,14 @@ class PagureFlaskGiveRepotests(tests.SimplePagureTest):
         self.assertEqual(project.user.user, user)
 
     def test_give_project_no_project(self):
-        """ Test the give_project endpoint. """
+        """Test the give_project endpoint."""
 
         # No such project
         output = self.app.post("/test42/give")
         self.assertEqual(output.status_code, 404)
 
     def test_give_project_no_csrf(self):
-        """ Test the give_project endpoint. """
+        """Test the give_project endpoint."""
 
         user = tests.FakeUser()
         user.username = "pingou"
@@ -82,7 +82,7 @@ class PagureFlaskGiveRepotests(tests.SimplePagureTest):
             self._check_user()
 
     def test_give_project_invalid_user(self):
-        """ Test the give_project endpoint. """
+        """Test the give_project endpoint."""
 
         user = tests.FakeUser()
         user.username = "pingou"
@@ -106,7 +106,7 @@ class PagureFlaskGiveRepotests(tests.SimplePagureTest):
             self._check_user()
 
     def test_give_project_no_user(self):
-        """ Test the give_project endpoint. """
+        """Test the give_project endpoint."""
 
         user = tests.FakeUser()
         user.username = "pingou"
@@ -129,7 +129,7 @@ class PagureFlaskGiveRepotests(tests.SimplePagureTest):
             self._check_user()
 
     def test_give_project_not_owner(self):
-        """ Test the give_project endpoint. """
+        """Test the give_project endpoint."""
 
         user = tests.FakeUser()
         user.username = "foo"
@@ -154,7 +154,7 @@ class PagureFlaskGiveRepotests(tests.SimplePagureTest):
             self._check_user()
 
     def test_give_project_not_admin(self):
-        """ Test the give_project endpoint. """
+        """Test the give_project endpoint."""
 
         user = tests.FakeUser()
         user.username = "foo"
@@ -179,7 +179,7 @@ class PagureFlaskGiveRepotests(tests.SimplePagureTest):
             self._check_user()
 
     def test_give_project_not_owner_but_is_admin(self):
-        """ Test the give_project endpoint. """
+        """Test the give_project endpoint."""
         project = pagure.lib.query.get_authorized_project(
             self.session, project_name="test"
         )
@@ -218,7 +218,7 @@ class PagureFlaskGiveRepotests(tests.SimplePagureTest):
     @patch.dict("pagure.config.config", {"PAGURE_ADMIN_USERS": "foo"})
     @patch("pagure.lib.git.generate_gitolite_acls", MagicMock())
     def test_give_project_not_owner_but_admin(self):
-        """ Test the give_project endpoint.
+        """Test the give_project endpoint.
 
         Test giving a project when the person giving the project is a pagure
         admin (instance wide admin) but not a project admin.
@@ -250,7 +250,7 @@ class PagureFlaskGiveRepotests(tests.SimplePagureTest):
     @patch.dict("pagure.config.config", {"PAGURE_ADMIN_USERS": "foo"})
     @patch("pagure.lib.git.generate_gitolite_acls", MagicMock())
     def test_give_project(self):
-        """ Test the give_project endpoint. """
+        """Test the give_project endpoint."""
 
         user = tests.FakeUser()
         user.username = "pingou"
@@ -282,8 +282,8 @@ class PagureFlaskGiveRepotests(tests.SimplePagureTest):
     @patch.dict("pagure.config.config", {"PAGURE_ADMIN_USERS": "foo"})
     @patch("pagure.lib.git.generate_gitolite_acls", MagicMock())
     def test_give_project_already_user(self):
-        """ Test the give_project endpoint when the new main_admin is already
-        a committer on the project. """
+        """Test the give_project endpoint when the new main_admin is already
+        a committer on the project."""
 
         project = pagure.lib.query._get_project(self.session, "test")
         pagure.lib.query.add_user_to_project(
@@ -327,7 +327,7 @@ class PagureFlaskGiveRepotests(tests.SimplePagureTest):
     @patch.dict("pagure.config.config", {"PAGURE_ADMIN_USERS": "foo"})
     @patch("pagure.lib.git.generate_gitolite_acls", MagicMock())
     def test_give_project_not_in_required_group(self):
-        """ Test the give_project endpoint. """
+        """Test the give_project endpoint."""
 
         user = tests.FakeUser()
         user.username = "pingou"
@@ -357,7 +357,7 @@ class PagureFlaskGiveRepotests(tests.SimplePagureTest):
     @patch.dict("pagure.config.config", {"PAGURE_ADMIN_USERS": "foo"})
     @patch("pagure.lib.git.generate_gitolite_acls", MagicMock())
     def test_give_project_in_required_group(self):
-        """ Test the give_project endpoint. """
+        """Test the give_project endpoint."""
 
         # Create the packager group
         msg = pagure.lib.query.add_group(
diff --git a/tests/test_pagure_flask_ui_app_index.py b/tests/test_pagure_flask_ui_app_index.py
index 5b67a4b..7ba08d7 100644
--- a/tests/test_pagure_flask_ui_app_index.py
+++ b/tests/test_pagure_flask_ui_app_index.py
@@ -30,10 +30,10 @@ import tests
 
 
 class PagureFlaskAppIndextests(tests.Modeltests):
-    """ Tests for the index page of flask app controller of pagure """
+    """Tests for the index page of flask app controller of pagure"""
 
     def test_index_logged_out(self):
-        """ Test the index endpoint when logged out. """
+        """Test the index endpoint when logged out."""
 
         output = self.app.get("/")
         self.assertEqual(output.status_code, 200)
@@ -57,8 +57,8 @@ class PagureFlaskAppIndextests(tests.Modeltests):
 
     def test_index_logged_in(self):
         """
-            Test the index endpoint when logged in.
-            It should redirect to the userdash.
+        Test the index endpoint when logged in.
+        It should redirect to the userdash.
         """
         tests.create_projects(self.session)
 
diff --git a/tests/test_pagure_flask_ui_app_userdash.py b/tests/test_pagure_flask_ui_app_userdash.py
index ea92bd6..1bcd4bd 100644
--- a/tests/test_pagure_flask_ui_app_userdash.py
+++ b/tests/test_pagure_flask_ui_app_userdash.py
@@ -30,11 +30,11 @@ import tests
 
 
 class PagureFlaskAppUserdashTests(tests.Modeltests):
-    """ Tests for the index page of flask app controller of pagure """
+    """Tests for the index page of flask app controller of pagure"""
 
     def test_index_commit_access_while_admin(self):
-        """ Test the index endpoint filter for commit access only when user
-        is an admin. """
+        """Test the index endpoint filter for commit access only when user
+        is an admin."""
         tests.create_projects(self.session)
 
         # Add a 3rd project just for foo
@@ -87,8 +87,8 @@ class PagureFlaskAppUserdashTests(tests.Modeltests):
             )
 
     def test_index_commit_access_while_commit(self):
-        """ Test the index endpoint filter for commit access only when user
-        is an committer. """
+        """Test the index endpoint filter for commit access only when user
+        is an committer."""
         tests.create_projects(self.session)
 
         # Add a 3rd project just for foo
@@ -138,8 +138,8 @@ class PagureFlaskAppUserdashTests(tests.Modeltests):
             )
 
     def test_index_commit_access_while_ticket(self):
-        """ Test the index endpoint filter for commit access only when user
-        is has ticket access. """
+        """Test the index endpoint filter for commit access only when user
+        is has ticket access."""
         tests.create_projects(self.session)
 
         # Add a 3rd project just for foo
@@ -189,8 +189,8 @@ class PagureFlaskAppUserdashTests(tests.Modeltests):
             )
 
     def test_index_admin_access_while_admin(self):
-        """ Test the index endpoint filter for admin access only when user
-        is an admin. """
+        """Test the index endpoint filter for admin access only when user
+        is an admin."""
         tests.create_projects(self.session)
 
         # Add a 3rd project just for foo
@@ -240,8 +240,8 @@ class PagureFlaskAppUserdashTests(tests.Modeltests):
             )
 
     def test_index_admin_access_while_commit(self):
-        """ Test the index endpoint filter for admin access only when user
-        is an committer. """
+        """Test the index endpoint filter for admin access only when user
+        is an committer."""
         tests.create_projects(self.session)
 
         # Add a 3rd project just for foo
@@ -292,8 +292,8 @@ class PagureFlaskAppUserdashTests(tests.Modeltests):
             )
 
     def test_index_main_admin_access_while_commit(self):
-        """ Test the index endpoint filter for main admin access only when
-        user is an committer. """
+        """Test the index endpoint filter for main admin access only when
+        user is an committer."""
         tests.create_projects(self.session)
 
         # Add a 3rd project just for foo
@@ -344,7 +344,7 @@ class PagureFlaskAppUserdashTests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})
     def test_index_logged_in_private_project(self):
-        """ Test the index endpoint when logged in with a private project. """
+        """Test the index endpoint when logged in with a private project."""
         tests.create_projects(self.session)
 
         # Add a 3rd project with a long description
diff --git a/tests/test_pagure_flask_ui_archives.py b/tests/test_pagure_flask_ui_archives.py
index 37ab61f..aeac5f1 100644
--- a/tests/test_pagure_flask_ui_archives.py
+++ b/tests/test_pagure_flask_ui_archives.py
@@ -29,10 +29,10 @@ from tests.test_pagure_lib_git_get_tags_objects import add_repo_tag
 
 
 class PagureFlaskUiArchivesTest(tests.Modeltests):
-    """ Tests checking the archiving mechanism. """
+    """Tests checking the archiving mechanism."""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskUiArchivesTest, self).setUp()
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -56,7 +56,7 @@ class PagureFlaskUiArchivesTest(tests.Modeltests):
         os.mkdir(self.archive_path)
 
     def test_project_no_conf(self):
-        """ Test getting the archive when pagure isn't configured. """
+        """Test getting the archive when pagure isn't configured."""
         output = self.app.get(
             "/somenamespace/test3/archive/tag1/test3-tag1.zip",
             follow_redirects=True,
@@ -72,7 +72,7 @@ class PagureFlaskUiArchivesTest(tests.Modeltests):
         self.assertEqual(os.listdir(self.archive_path), [])
 
     def test_project_invalid_conf(self):
-        """ Test getting the archive when pagure is wrongly configured. """
+        """Test getting the archive when pagure is wrongly configured."""
         with mock.patch.dict(
             "pagure.config.config",
             {"ARCHIVE_FOLDER": os.path.join(self.path, "invalid")},
@@ -91,7 +91,7 @@ class PagureFlaskUiArchivesTest(tests.Modeltests):
         self.assertEqual(os.listdir(self.archive_path), [])
 
     def test_project_invalid_format(self):
-        """ Test getting the archive when the format provided is invalid. """
+        """Test getting the archive when the format provided is invalid."""
         with mock.patch.dict(
             "pagure.config.config",
             {"ARCHIVE_FOLDER": os.path.join(self.path, "archives")},
@@ -106,7 +106,7 @@ class PagureFlaskUiArchivesTest(tests.Modeltests):
         self.assertEqual(os.listdir(self.archive_path), [])
 
     def test_project_no_commit(self):
-        """ Test getting the archive of an empty project. """
+        """Test getting the archive of an empty project."""
         with mock.patch.dict(
             "pagure.config.config",
             {"ARCHIVE_FOLDER": os.path.join(self.path, "archives")},
@@ -124,8 +124,8 @@ class PagureFlaskUiArchivesTest(tests.Modeltests):
         self.assertEqual(os.listdir(self.archive_path), [])
 
     def test_project_no_tag(self):
-        """ Test getting the archive of a non-empty project but without
-        tags. """
+        """Test getting the archive of a non-empty project but without
+        tags."""
         with mock.patch.dict(
             "pagure.config.config",
             {"ARCHIVE_FOLDER": os.path.join(self.path, "archives")},
@@ -142,7 +142,7 @@ class PagureFlaskUiArchivesTest(tests.Modeltests):
         self.assertEqual(os.listdir(self.archive_path), [])
 
     def test_project_no_tag(self):
-        """ Test getting the archive of an empty project. """
+        """Test getting the archive of an empty project."""
         with mock.patch.dict(
             "pagure.config.config",
             {"ARCHIVE_FOLDER": os.path.join(self.path, "archives")},
@@ -159,7 +159,7 @@ class PagureFlaskUiArchivesTest(tests.Modeltests):
         self.assertEqual(os.listdir(self.archive_path), [])
 
     def test_project_w_tag_zip(self):
-        """ Test getting the archive from a tag. """
+        """Test getting the archive from a tag."""
         with mock.patch.dict(
             "pagure.config.config",
             {"ARCHIVE_FOLDER": os.path.join(self.path, "archives")},
@@ -201,7 +201,7 @@ class PagureFlaskUiArchivesTest(tests.Modeltests):
         )
 
     def test_project_w_tag_tar(self):
-        """ Test getting the archive from a tag. """
+        """Test getting the archive from a tag."""
         with mock.patch.dict(
             "pagure.config.config",
             {"ARCHIVE_FOLDER": os.path.join(self.path, "archives")},
@@ -243,7 +243,7 @@ class PagureFlaskUiArchivesTest(tests.Modeltests):
         )
 
     def test_project_w_tag_tar_gz(self):
-        """ Test getting the archive from a tag. """
+        """Test getting the archive from a tag."""
         with mock.patch.dict(
             "pagure.config.config",
             {"ARCHIVE_FOLDER": os.path.join(self.path, "archives")},
@@ -285,7 +285,7 @@ class PagureFlaskUiArchivesTest(tests.Modeltests):
         )
 
     def test_project_w_commit_tar_gz(self):
-        """ Test getting the archive from a commit. """
+        """Test getting the archive from a commit."""
         repopath = os.path.join(self.path, "repos", "test.git")
         repo = pygit2.Repository(repopath)
         commit = repo.head.target.hex
@@ -310,8 +310,8 @@ class PagureFlaskUiArchivesTest(tests.Modeltests):
         )
 
     def test_project_w_commit_tar_gz_twice(self):
-        """ Test getting the archive from a commit twice, so we hit the
-        disk cache. """
+        """Test getting the archive from a commit twice, so we hit the
+        disk cache."""
         repopath = os.path.join(self.path, "repos", "test.git")
         repo = pygit2.Repository(repopath)
         commit = repo.head.target.hex
diff --git a/tests/test_pagure_flask_ui_boards.py b/tests/test_pagure_flask_ui_boards.py
index f52e820..367e808 100644
--- a/tests/test_pagure_flask_ui_boards.py
+++ b/tests/test_pagure_flask_ui_boards.py
@@ -71,6 +71,7 @@ def set_up_board(self):
                         "tag_color": "DeepBlueSky",
                         "tag_description": "",
                     },
+                    "full_url": "http://localhost.localdomain/test/boards/dev",
                 }
             ]
         },
@@ -78,7 +79,7 @@ def set_up_board(self):
 
 
 class PagureFlaskUiBoardstests(tests.SimplePagureTest):
-    """ Tests for flask UI Boards controller of pagure """
+    """Tests for flask UI Boards controller of pagure"""
 
     maxDiff = None
 
@@ -187,7 +188,7 @@ class PagureFlaskUiBoardstests(tests.SimplePagureTest):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_issue_add_tags_check_board_not_tagged(self):
-        """ Test the update_issue endpoint. """
+        """Test the update_issue endpoint."""
 
         # Before update, list tags
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -243,7 +244,7 @@ class PagureFlaskUiBoardstests(tests.SimplePagureTest):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_issue_add_tags_check_board(self):
-        """ Test the update_issue endpoint. """
+        """Test the update_issue endpoint."""
 
         # Before update, list tags
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -299,7 +300,7 @@ class PagureFlaskUiBoardstests(tests.SimplePagureTest):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_issue_add_tags_check_board_remove_tag_check_board(self):
-        """ Test the update_issue endpoint. """
+        """Test the update_issue endpoint."""
 
         # Before update, list tags
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -602,6 +603,7 @@ class PagureFlaskUiBoardstests(tests.SimplePagureTest):
                             "tag_color": "DeepBlueSky",
                             "tag_description": "",
                         },
+                        "full_url": "http://localhost.localdomain/test/boards/dev",
                     },
                     "rank": 1,
                     "status": {
@@ -633,6 +635,7 @@ class PagureFlaskUiBoardstests(tests.SimplePagureTest):
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                 }
             ],
@@ -640,6 +643,7 @@ class PagureFlaskUiBoardstests(tests.SimplePagureTest):
             "custom_fields": [],
             "date_created": "1594654596",
             "depends": [],
+            "full_url": "http://localhost.localdomain/test/issue/1",
             "id": 1,
             "last_updated": "1594654596",
             "milestone": None,
@@ -655,6 +659,7 @@ class PagureFlaskUiBoardstests(tests.SimplePagureTest):
                 "fullname": "PY C",
                 "name": "pingou",
                 "url_path": "user/pingou",
+                "full_url": "http://localhost.localdomain/user/pingou",
             },
         }
 
diff --git a/tests/test_pagure_flask_ui_clone.py b/tests/test_pagure_flask_ui_clone.py
index 11e9950..a16e48d 100644
--- a/tests/test_pagure_flask_ui_clone.py
+++ b/tests/test_pagure_flask_ui_clone.py
@@ -32,7 +32,7 @@ import tests
 
 
 class PagureFlaskAppClonetests(tests.Modeltests):
-    """ Tests for the clone bridging. """
+    """Tests for the clone bridging."""
 
     def setUp(self):
         super(PagureFlaskAppClonetests, self).setUp()
@@ -48,7 +48,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"ALLOW_HTTP_PULL_PUSH": False})
     def test_http_clone_disabled(self):
-        """ Test that the HTTP clone endpoint gets correctly closed. """
+        """Test that the HTTP clone endpoint gets correctly closed."""
         output = self.app.get(
             "/clonetest.git/info/refs?service=git-upload-pack"
         )
@@ -57,14 +57,14 @@ class PagureFlaskAppClonetests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"ALLOW_HTTP_PULL_PUSH": True})
     def test_http_clone_invalid_service(self):
-        """ Test that the HTTP endpoint refuses invalid services. """
+        """Test that the HTTP endpoint refuses invalid services."""
         output = self.app.get("/clonetest.git/info/refs?service=myservice")
         self.assertEqual(output.status_code, 400)
         self.assertIn("Unknown service", output.get_data(as_text=True))
 
     @patch.dict("pagure.config.config", {"ALLOW_HTTP_PULL_PUSH": True})
     def test_http_clone_invalid_project(self):
-        """ Test that the HTTP endpoint refuses invalid projects. """
+        """Test that the HTTP endpoint refuses invalid projects."""
         output = self.app.get(
             "/nosuchrepo.git/info/refs?service=git-upload-pack"
         )
@@ -73,7 +73,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"ALLOW_HTTP_PULL_PUSH": True})
     def test_http_clone_dumb(self):
-        """ Test that the HTTP endpoint refuses dumb service request. """
+        """Test that the HTTP endpoint refuses dumb service request."""
         output = self.app.get("/clonetest.git/info/refs")
         self.assertEqual(output.status_code, 400)
         self.assertIn("Please switch", output.get_data(as_text=True))
@@ -87,7 +87,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
         },
     )
     def test_http_push_disabled(self):
-        """ Test that the HTTP push gets refused. """
+        """Test that the HTTP push gets refused."""
         output = self.app.get(
             "/clonetest.git/info/refs?service=git-receive-pack"
         )
@@ -106,7 +106,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
         },
     )
     def test_http_push_unauthed(self):
-        """ Test that the HTTP push gets refused unauthed. """
+        """Test that the HTTP push gets refused unauthed."""
         output = self.app.get(
             "/clonetest.git/info/refs?service=git-receive-pack"
         )
@@ -115,7 +115,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"ALLOW_HTTP_PULL_PUSH": True})
     def test_http_clone_private_project_unauthed(self):
-        """ Test that the HTTP endpoint enforced project.private. """
+        """Test that the HTTP endpoint enforced project.private."""
         project = pagure.lib.query._get_project(self.session, "clonetest")
         project.private = True
         self.session.add(project)
@@ -136,7 +136,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
         },
     )
     def test_http_clone(self):
-        """ Test that HTTP cloning gives reasonable output. """
+        """Test that HTTP cloning gives reasonable output."""
         # Unfortunately, actually testing a git clone would need the app to
         # run on a TCP port, which the test environment doesn't do.
 
@@ -165,7 +165,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
         },
     )
     def test_http_clone_private(self):
-        """ Test that HTTP cloning gives reasonable output with project.private. """
+        """Test that HTTP cloning gives reasonable output with project.private."""
         # Unfortunately, actually testing a git clone would need the app to
         # run on a TCP port, which the test environment doesn't do.
         project = pagure.lib.query._get_project(self.session, "clonetest")
@@ -197,7 +197,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
         },
     )
     def test_http_push(self):
-        """ Test that the HTTP push gets accepted. """
+        """Test that the HTTP push gets accepted."""
         output = self.app.get(
             "/clonetest.git/info/refs?service=git-receive-pack",
             environ_overrides={"REMOTE_USER": "pingou"},
@@ -216,14 +216,15 @@ class PagureFlaskAppClonetests(tests.Modeltests):
         },
     )
     def test_http_push_api_token(self):
-        """ Test that the HTTP push gets accepted. """
+        """Test that the HTTP push gets accepted."""
 
         headers = {
             "Authorization": b"Basic %s"
             % base64.b64encode(b"pingou:aaabbbcccddd")
         }
         output = self.app.get(
-            "/test.git/info/refs?service=git-receive-pack", headers=headers,
+            "/test.git/info/refs?service=git-receive-pack",
+            headers=headers,
         )
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
@@ -239,7 +240,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
         },
     )
     def test_http_push_projectless_api_token(self):
-        """ Test that the HTTP push gets accepted. """
+        """Test that the HTTP push gets accepted."""
         tests.create_tokens(self.session, project_id=None, suffix="2")
         tests.create_tokens_acl(
             self.session, token_id="aaabbbcccddd2", acl_name="commit"
@@ -267,7 +268,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
         },
     )
     def test_http_push__invalid_project_for_api_token(self):
-        """ Test that the HTTP push gets accepted. """
+        """Test that the HTTP push gets accepted."""
 
         headers = {
             "Authorization": b"Basic %s"
@@ -289,7 +290,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
         },
     )
     def test_http_push_api_token_invalid_user(self):
-        """ Test that the HTTP push gets accepted. """
+        """Test that the HTTP push gets accepted."""
 
         headers = {
             "Authorization": b"Basic %s"
@@ -311,7 +312,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
         },
     )
     def test_http_push_invalid_api_token(self):
-        """ Test that the HTTP push gets accepted. """
+        """Test that the HTTP push gets accepted."""
 
         headers = {
             "Authorization": b"Basic %s"
@@ -333,7 +334,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
         },
     )
     def test_http_push_invalid_acl_on_token(self):
-        """ Test that the HTTP push gets accepted. """
+        """Test that the HTTP push gets accepted."""
         tests.create_tokens(self.session, suffix="2")
         tests.create_tokens_acl(
             self.session, token_id="aaabbbcccddd2", acl_name="commit_flag"
@@ -344,7 +345,8 @@ class PagureFlaskAppClonetests(tests.Modeltests):
             % base64.b64encode(b"pingou:aaabbbcccddd2")
         }
         output = self.app.get(
-            "/test.git/info/refs?service=git-receive-pack", headers=headers,
+            "/test.git/info/refs?service=git-receive-pack",
+            headers=headers,
         )
         self.assertEqual(output.status_code, 401)
         self.assertIn("Authorization Required", output.get_data(as_text=True))
@@ -359,7 +361,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
         },
     )
     def test_http_push_local_auth(self):
-        """ Test that the HTTP push gets accepted. """
+        """Test that the HTTP push gets accepted."""
 
         headers = {
             "Authorization": b"Basic %s" % base64.b64encode(b"pingou:foo")
@@ -383,7 +385,7 @@ class PagureFlaskAppClonetests(tests.Modeltests):
         },
     )
     def test_http_push_local_auth_invalid_username(self):
-        """ Test that the HTTP push gets accepted. """
+        """Test that the HTTP push gets accepted."""
 
         headers = {
             "Authorization": b"Basic %s" % base64.b64encode(b"invalid:foo")
diff --git a/tests/test_pagure_flask_ui_fork.py b/tests/test_pagure_flask_ui_fork.py
index 21ecd7d..a63f691 100644
--- a/tests/test_pagure_flask_ui_fork.py
+++ b/tests/test_pagure_flask_ui_fork.py
@@ -19,11 +19,13 @@ import time
 import os
 import re
 
+import pagure_messages
 import pygit2
 import six
-from mock import patch, MagicMock
 from bs4 import BeautifulSoup
 from datetime import datetime, timedelta
+from fedora_messaging import api, testing
+from mock import ANY, patch, MagicMock
 
 sys.path.insert(
     0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
@@ -36,7 +38,7 @@ from pagure.lib.repo import PagureRepo
 
 
 def _get_commits(output):
-    """ Returns the commits message in the output. All commits must have
+    """Returns the commits message in the output. All commits must have
     been made by `Alice Author` or `PY C` to be found.
     """
     commits = []
@@ -73,7 +75,7 @@ def set_up_git_repo(
     prid=1,
     name_from="test",
 ):
-    """ Set up the git repo and create the corresponding PullRequest
+    """Set up the git repo and create the corresponding PullRequest
     object.
     """
 
@@ -227,10 +229,10 @@ def set_up_git_repo(
 
 
 class PagureFlaskForktests(tests.Modeltests):
-    """ Tests for flask fork controller of pagure """
+    """Tests for flask fork controller of pagure"""
 
     def test_request_pull_reference(self):
-        """ Test if there is a reference created for a new PR. """
+        """Test if there is a reference created for a new PR."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(
@@ -257,7 +259,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_request_pull(self, send_email):
-        """ Test the request_pull endpoint. """
+        """Test the request_pull endpoint."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -289,7 +291,7 @@ class PagureFlaskForktests(tests.Modeltests):
         #'  PR from the feature branch\n</h3>',
         # output_text)
         self.assertIn(
-            'title="View file as of 2a552b">sources</a>', output_text
+            'title="View file as of 2a552bb">sources</a>', output_text
         )
 
         # Test if the `open changed file icon` is displayed.
@@ -302,17 +304,37 @@ class PagureFlaskForktests(tests.Modeltests):
         )
 
         self.assertIn(
-            '<span class="btn btn-success btn-sm font-weight-bold disabled opacity-100">+3</span>',
+            '<span class="btn btn-success btn-sm font-weight-bold disabled'
+            ' opacity-100">+3</span>',
             output_text,
         )
         self.assertIn(
-            '<span class="btn btn-danger btn-sm font-weight-bold disabled opacity-100">-1</span>',
+            '<span class="btn btn-danger btn-sm font-weight-bold disabled '
+            'opacity-100">-1</span>',
+            output_text,
+        )
+
+        # Test if hunk headline is rendered without line numbers
+        self.assertIn(
+            '<td class="cell1"></td><td class="prc border-right"></td>\n<td '
+            'class="cell2 stretch-table-column">                <pre class='
+            '"text-muted"><code>@@ -1,2 +1,4 @@',
+            output_text,
+        )
+        # Tests if line number 1 is displayed
+        self.assertNotIn(
+            '<td class="cell1"><a id="_1__1" href="#_1__1" data-line-number="1" data-file-number="1"></a></td>',
+            output_text,
+        )
+        # Test if line number 2 is displayed
+        self.assertIn(
+            '<td class="cell1"><a id="_1__2" href="#_1__2" data-line-number="2" data-file-number="1"></a></td>',
             output_text,
         )
 
     @patch("pagure.lib.notify.send_email")
     def test_request_pull_delete_branch_button_no_auth(self, send_email):
-        """ Test the request_pull endpoint. """
+        """Test the request_pull endpoint."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -337,7 +359,7 @@ class PagureFlaskForktests(tests.Modeltests):
             output_text,
         )
         self.assertIn(
-            'title="View file as of 2a552b">sources</a>', output_text
+            'title="View file as of 2a552bb">sources</a>', output_text
         )
         # Un-authenticated user cannot see this checkbox
         self.assertNotIn(
@@ -349,7 +371,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_request_pull_delete_branch_button(self, send_email):
-        """ Test the request_pull endpoint. """
+        """Test the request_pull endpoint."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -377,7 +399,7 @@ class PagureFlaskForktests(tests.Modeltests):
                 output_text,
             )
             self.assertIn(
-                'title="View file as of 2a552b">sources</a>', output_text
+                'title="View file as of 2a552bb">sources</a>', output_text
             )
             self.assertIn(
                 '<input id="delete_branch" name="delete_branch" type="checkbox" '
@@ -390,7 +412,7 @@ class PagureFlaskForktests(tests.Modeltests):
     def test_request_pull_delete_branch_button_no_project_from(
         self, send_email
     ):
-        """ Test the request_pull endpoint. """
+        """Test the request_pull endpoint."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -421,7 +443,7 @@ class PagureFlaskForktests(tests.Modeltests):
                 output_text,
             )
             self.assertIn(
-                'title="View file as of 2a552b">sources</a>', output_text
+                'title="View file as of 2a552bb">sources</a>', output_text
             )
             self.assertIn(
                 '<input id="delete_branch" name="delete_branch" type="checkbox" '
@@ -434,7 +456,7 @@ class PagureFlaskForktests(tests.Modeltests):
     def test_request_pull_delete_branch_button_no_project_from_no_acl(
         self, send_email
     ):
-        """ Test the request_pull endpoint. """
+        """Test the request_pull endpoint."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -465,7 +487,7 @@ class PagureFlaskForktests(tests.Modeltests):
                 output_text,
             )
             self.assertIn(
-                'title="View file as of 2a552b">sources</a>', output_text
+                'title="View file as of 2a552bb">sources</a>', output_text
             )
             self.assertNotIn(
                 '<input id="delete_branch" name="delete_branch" type="checkbox" '
@@ -476,7 +498,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_task_update_request_pull(self, send_email):
-        """ Test the task update_pull_request endpoint. """
+        """Test the task update_pull_request endpoint."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -506,7 +528,7 @@ class PagureFlaskForktests(tests.Modeltests):
             output_text,
         )
         self.assertIn(
-            'title="View file as of 2a552b">sources</a>', output_text
+            'title="View file as of 2a552bb">sources</a>', output_text
         )
 
         # Add a new commit on the repo from
@@ -566,7 +588,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_request_pull_ci_dropdown(self, send_email):
-        """ Test presence of the "Rerun CI" dropdown with various settings. """
+        """Test presence of the "Rerun CI" dropdown with various settings."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -676,7 +698,7 @@ class PagureFlaskForktests(tests.Modeltests):
         {"TRIGGER_CI": {"CI1": {"name": "CI1", "description": "CI1!"}}},
     )
     def test_request_pull_ci_rerun(self, send_email):
-        """ Test rerunning CI using button from the "Rerun CI" dropdown. """
+        """Test rerunning CI using button from the "Rerun CI" dropdown."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -741,7 +763,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_merge_request_pull_FF(self, send_email):
-        """ Test the merge_request_pull endpoint with a FF PR. """
+        """Test the merge_request_pull endpoint with a FF PR."""
         send_email.return_value = True
 
         self.test_request_pull()
@@ -769,7 +791,7 @@ class PagureFlaskForktests(tests.Modeltests):
             #'  PR from the feature branch\n</h3>',
             # output_text)
             self.assertIn(
-                'title="View file as of 2a552b">sources</a>', output_text
+                'title="View file as of 2a552bb">sources</a>', output_text
             )
 
             # Wrong project
@@ -942,7 +964,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_merge_request_pull_merge(self, send_email):
-        """ Test the merge_request_pull endpoint with a merge PR. """
+        """Test the merge_request_pull endpoint with a merge PR."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -985,7 +1007,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_merge_request_pull_merge_with_comment(self, send_email):
-        """ Test the merge_request_pull endpoint with a merge PR. """
+        """Test the merge_request_pull endpoint with a merge PR."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1045,7 +1067,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_merge_request_pull_merge_with_delete_branch(self, send_email):
-        """ Test the merge_request_pull endpoint with a merge PR and delete source branch. """
+        """Test the merge_request_pull endpoint with a merge PR and delete source branch."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1088,7 +1110,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_merge_request_pull_conflicts(self, send_email):
-        """ Test the merge_request_pull endpoint with a conflicting PR. """
+        """Test the merge_request_pull endpoint with a conflicting PR."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1132,7 +1154,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_merge_request_pull_conflicts_with_delete_branch(self, send_email):
-        """ Test the merge_request_pull endpoint with a conflicting PR and request deletion of branch. """
+        """Test the merge_request_pull endpoint with a conflicting PR and request deletion of branch."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1179,7 +1201,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_merge_request_pull_nochange(self, send_email):
-        """ Test the merge_request_pull endpoint. """
+        """Test the merge_request_pull endpoint."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1222,7 +1244,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_request_pull_close(self, send_email):
-        """ Test the request_pull endpoint with a closed PR. """
+        """Test the request_pull endpoint with a closed PR."""
         send_email.return_value = True
 
         self.test_merge_request_pull_FF()
@@ -1232,12 +1254,12 @@ class PagureFlaskForktests(tests.Modeltests):
         output_text = output.get_data(as_text=True)
         self.assertIsNotNone(re.search(MERGED_PATTERN, output_text))
         self.assertIn(
-            'title="View file as of 2a552b">sources</a>', output_text
+            'title="View file as of 2a552bb">sources</a>', output_text
         )
 
     @patch("pagure.lib.notify.send_email")
     def test_request_pull_disabled(self, send_email):
-        """ Test the request_pull endpoint with PR disabled. """
+        """Test the request_pull endpoint with PR disabled."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1262,7 +1284,7 @@ class PagureFlaskForktests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.lib.git.update_pull_ref")
     def test_request_pull_empty_repo(self, send_email, update_pull_ref):
-        """ Test the request_pull endpoint against an empty repo. """
+        """Test the request_pull endpoint against an empty repo."""
         # Mock update_pull_ref or the repo won't be empty anymore
         # (the PR will have been pushed to refs/pull)
         send_email.return_value = True
@@ -1353,7 +1375,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_request_pull_empty_fork(self, send_email):
-        """ Test the request_pull endpoint from an empty fork. """
+        """Test the request_pull endpoint from an empty fork."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1588,7 +1610,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_request_pulls(self, send_email):
-        """ Test the request_pulls endpoint. """
+        """Test the request_pulls endpoint."""
         send_email.return_value = True
 
         # No such project
@@ -1854,7 +1876,7 @@ class PagureFlaskForktests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_request_pull_patch(self, send_email):
-        """ Test the request_pull_patch endpoint. """
+        """Test the request_pull_patch endpoint."""
         send_email.return_value = True
 
         output = self.app.get("/test/pull-request/1.patch")
@@ -1934,7 +1956,7 @@ index 9f44358..2a552bb 100644
 
     @patch("pagure.lib.notify.send_email")
     def test_request_pull_diff(self, send_email):
-        """ Test the request_pull_patch endpoint. """
+        """Test the request_pull_patch endpoint."""
         send_email.return_value = True
 
         output = self.app.get("/test/pull-request/1.diff")
@@ -1995,7 +2017,7 @@ index 9f44358..2a552bb 100644
 
     @patch("pagure.lib.notify.send_email")
     def test_request_pull_patch_close(self, send_email):
-        """ Test the request_pull_patch endpoint with a closed PR. """
+        """Test the request_pull_patch endpoint with a closed PR."""
         send_email.return_value = True
 
         self.test_merge_request_pull_FF()
@@ -2041,7 +2063,7 @@ index 9f44358..2a552bb 100644
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.lib.git.update_pull_ref")
     def test_request_pull_patch_empty_repo(self, send_email, update_pull_ref):
-        """ Test the request_pull_patch endpoint against an empty repo. """
+        """Test the request_pull_patch endpoint against an empty repo."""
         # Mock update_pull_ref or the repo won't be empty anymore
         # (the PR will have been pushed to refs/pull)
         send_email.return_value = True
@@ -2156,7 +2178,7 @@ index 0000000..2a552bb
 
     @patch("pagure.lib.notify.send_email")
     def test_request_pull_patch_empty_fork(self, send_email):
-        """ Test the request_pull_patch endpoint from an empty fork. """
+        """Test the request_pull_patch endpoint from an empty fork."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -2221,7 +2243,7 @@ index 0000000..2a552bb
 
     @patch("pagure.lib.notify.send_email")
     def test_close_request_pull(self, send_email):
-        """ Test the close_request_pull endpoint. """
+        """Test the close_request_pull endpoint."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -2315,9 +2337,12 @@ index 0000000..2a552bb
             )
             self.assertIn("Pull request closed!", output_text)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.notify.send_email")
     def test_reopen_request_pull(self, send_email):
-        """ Test the reopen_request_pull endpoint. """
+        """Test the reopen_request_pull endpoint."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -2402,16 +2427,640 @@ index 0000000..2a552bb
             )
             settings = repo.settings
             settings["pull_requests"] = True
+            settings["fedmsg_notifications"] = True
             repo.settings = settings
             self.session.add(repo)
             self.session.commit()
 
-            output = self.app.post(
-                "/test/pull-request/cancel/1", data=data, follow_redirects=True
-            )
-            output = self.app.post(
-                "/test/pull-request/1/reopen", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.PullRequestCommentAddedV1(
+                    topic="pagure.pull-request.comment.added",
+                    body={
+                        "pullrequest": {
+                            "id": 1,
+                            "uid": ANY,
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "title": "PR from the feature branch",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "url_path": "test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": None,
+                            "status": "Closed",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [
+                                {
+                                    "id": 1,
+                                    "commit": None,
+                                    "tree": None,
+                                    "filename": None,
+                                    "line": None,
+                                    "comment": "Pull-Request has been closed by pingou",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": True,
+                                    "reactions": {},
+                                }
+                            ],
+                        },
+                        "agent": "pingou",
+                    },
+                ),
+                pagure_messages.PullRequestClosedV1(
+                    topic="pagure.pull-request.closed",
+                    body={
+                        "pullrequest": {
+                            "id": 1,
+                            "uid": ANY,
+                            "title": "PR from the feature branch",
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "url_path": "test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": None,
+                            "status": "Closed",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [
+                                {
+                                    "id": 1,
+                                    "commit": None,
+                                    "tree": None,
+                                    "filename": None,
+                                    "line": None,
+                                    "comment": "Pull-Request has been closed by pingou",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": True,
+                                    "reactions": {},
+                                }
+                            ],
+                        },
+                        "merged": False,
+                        "agent": "pingou",
+                    },
+                ),
+            ):
+                output = self.app.post(
+                    "/test/pull-request/close/1",
+                    data=data,
+                    follow_redirects=True,
+                )
+                self.assertEqual(output.status_code, 200)
+
+            with testing.mock_sends(
+                pagure_messages.PullRequestCommentAddedV1(
+                    topic="pagure.pull-request.comment.added",
+                    body={
+                        "pullrequest": {
+                            "id": 1,
+                            "uid": ANY,
+                            "title": "PR from the feature branch",
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": None,
+                            "status": "Open",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [
+                                {
+                                    "id": 1,
+                                    "commit": None,
+                                    "tree": None,
+                                    "filename": None,
+                                    "line": None,
+                                    "comment": "Pull-Request has been closed by pingou",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": True,
+                                    "reactions": {},
+                                },
+                                {
+                                    "id": 2,
+                                    "commit": None,
+                                    "tree": None,
+                                    "filename": None,
+                                    "line": None,
+                                    "comment": "Pull-Request has been reopened by pingou",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": True,
+                                    "reactions": {},
+                                },
+                            ],
+                        },
+                        "agent": "pingou",
+                    },
+                ),
+                pagure_messages.PullRequestReopenedV1(
+                    topic="pagure.pull-request.reopened",
+                    body={
+                        "pullrequest": {
+                            "id": 1,
+                            "uid": ANY,
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "title": "PR from the feature branch",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": None,
+                            "status": "Open",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [
+                                {
+                                    "id": 1,
+                                    "commit": None,
+                                    "tree": None,
+                                    "filename": None,
+                                    "line": None,
+                                    "comment": "Pull-Request has been closed by pingou",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": True,
+                                    "reactions": {},
+                                },
+                                {
+                                    "id": 2,
+                                    "commit": None,
+                                    "tree": None,
+                                    "filename": None,
+                                    "line": None,
+                                    "comment": "Pull-Request has been reopened by pingou",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": True,
+                                    "reactions": {},
+                                },
+                            ],
+                        },
+                        "agent": "pingou",
+                    },
+                ),
+            ):
+                output = self.app.post(
+                    "/test/pull-request/1/reopen",
+                    data=data,
+                    follow_redirects=True,
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -2424,10 +3073,12 @@ index 0000000..2a552bb
                 output_text,
             )
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_pull_requests_assign(self):
-        """ Test the update_pull_requests endpoint when assigning a PR.
-        """
+        """Test the update_pull_requests endpoint when assigning a PR."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(
@@ -2534,9 +3185,455 @@ index 0000000..2a552bb
 
         user.username = "pingou"
         with tests.user_set(self.app.application, user):
-            output = self.app.post(
-                "/test/pull-request/1/update", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                api.Message(
+                    topic="pagure.request.assigned.added",
+                    body={
+                        "request": {
+                            "id": 1,
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "uid": ANY,
+                            "title": "PR from the feature branch",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                                "url_path": "user/pingou",
+                            },
+                            "status": "Open",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [],
+                        },
+                        "pullrequest": {
+                            "id": 1,
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "uid": ANY,
+                            "title": "PR from the feature branch",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "url_path": "test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "status": "Open",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "url_path": "test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "agent": "pingou",
+                    },
+                ),
+                pagure_messages.PullRequestAssignedAddedV1(
+                    topic="pagure.pull-request.assigned.added",
+                    body={
+                        "pullrequest": {
+                            "id": 1,
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "uid": ANY,
+                            "title": "PR from the feature branch",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "status": "Open",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "agent": "pingou",
+                    },
+                ),
+            ):
+                output = self.app.post(
+                    "/test/pull-request/1/update",
+                    data=data,
+                    follow_redirects=True,
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -2555,7 +3652,7 @@ index 0000000..2a552bb
             )
             self.assertIn("Request assigned", output_text)
 
-            # Pull-Request closed
+            # Pull-Request closed - reset assignee
             repo = pagure.lib.query.get_authorized_project(
                 self.session, "test"
             )
@@ -2565,9 +3662,505 @@ index 0000000..2a552bb
             self.session.add(req)
             self.session.commit()
 
-            output = self.app.post(
-                "/test/pull-request/1/update", data=data, follow_redirects=True
-            )
+            data = {"csrf_token": csrf_token, "user": None}
+
+            with testing.mock_sends(
+                api.Message(
+                    topic="pagure.request.assigned.reset",
+                    body={
+                        "request": {
+                            "id": 1,
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "uid": ANY,
+                            "title": "PR from the feature branch",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": None,
+                            "status": "Closed",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [
+                                {
+                                    "id": 1,
+                                    "commit": None,
+                                    "tree": None,
+                                    "filename": None,
+                                    "line": None,
+                                    "comment": "**Metadata Update from @pingou**:\n- Request assigned",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": True,
+                                    "reactions": {},
+                                }
+                            ],
+                        },
+                        "pullrequest": {
+                            "id": 1,
+                            "uid": ANY,
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "title": "PR from the feature branch",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "url_path": "test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": None,
+                            "status": "Closed",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [
+                                {
+                                    "id": 1,
+                                    "commit": None,
+                                    "tree": None,
+                                    "filename": None,
+                                    "line": None,
+                                    "comment": "**Metadata Update from @pingou**:\n- Request assigned",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": True,
+                                    "reactions": {},
+                                }
+                            ],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "agent": "pingou",
+                    },
+                ),
+                pagure_messages.PullRequestAssignedResetV1(
+                    topic="pagure.pull-request.assigned.reset",
+                    body={
+                        "pullrequest": {
+                            "id": 1,
+                            "uid": ANY,
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "title": "PR from the feature branch",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": None,
+                            "status": "Closed",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [
+                                {
+                                    "id": 1,
+                                    "commit": None,
+                                    "tree": None,
+                                    "filename": None,
+                                    "line": None,
+                                    "comment": "**Metadata Update from @pingou**:\n- Request assigned",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": True,
+                                    "reactions": {},
+                                }
+                            ],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "agent": "pingou",
+                    },
+                ),
+            ):
+                output = self.app.post(
+                    "/test/pull-request/1/update",
+                    data=data,
+                    follow_redirects=True,
+                )
             self.assertEqual(output.status_code, 200)
 
             # Project w/o pull-request
@@ -2585,10 +4178,12 @@ index 0000000..2a552bb
             )
             self.assertEqual(output.status_code, 404)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_pull_requests_tag(self):
-        """ Test the update_pull_requests endpoint when tagging a PR.
-        """
+        """Test the update_pull_requests endpoint when tagging a PR."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(
@@ -2633,10 +4228,176 @@ index 0000000..2a552bb
             # Tag the PR
             data = {"csrf_token": csrf_token, "tag": "black"}
 
-            output = self.app.post(
-                "/test/pull-request/1/update", data=data, follow_redirects=True
-            )
-            self.assertEqual(output.status_code, 200)
+            with testing.mock_sends(
+                pagure_messages.PullRequestTagAddedV1(
+                    topic="pagure.pull-request.tag.added",
+                    body={
+                        # This is field is for backward compatibility but we
+                        # don't want to check it
+                        "pull_request": ANY,
+                        "pullrequest": {
+                            "id": 1,
+                            "uid": ANY,
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "title": "PR from the feature branch",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": None,
+                            "status": "Open",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": ["black"],
+                            "comments": [],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "tags": ["black"],
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/pull-request/1/update",
+                    data=data,
+                    follow_redirects=True,
+                )
+                self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
                 "<title>PR#1: PR from the feature branch - test\n - "
@@ -2682,9 +4443,381 @@ index 0000000..2a552bb
             # Re-try to tag the PR
             data = {"csrf_token": csrf_token, "tag": "blue, yellow"}
 
-            output = self.app.post(
-                "/test/pull-request/1/update", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.PullRequestTagAddedV1(
+                    topic="pagure.pull-request.tag.added",
+                    body={
+                        "pull_request": ANY,
+                        "pullrequest": {
+                            "id": 1,
+                            "uid": ANY,
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "title": "PR from the feature branch",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "foo",
+                                "fullname": "foo bar",
+                                "url_path": "user/foo",
+                                "full_url": "http://localhost.localdomain/user/foo",
+                            },
+                            "assignee": None,
+                            "status": "Open",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": ["black", "blue", "yellow"],
+                            "comments": [
+                                {
+                                    "id": 1,
+                                    "commit": None,
+                                    "tree": None,
+                                    "filename": None,
+                                    "line": None,
+                                    "comment": "**Metadata Update from "
+                                    "@pingou**:\n- Pull-request tagged "
+                                    "with: black",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": True,
+                                    "reactions": {},
+                                }
+                            ],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "tags": ["blue", "yellow"],
+                        "agent": "foo",
+                    },
+                ),
+                pagure_messages.PullRequestTagRemovedV1(
+                    topic="pagure.pull-request.tag.removed",
+                    body={
+                        # This is field is for backward compatibility but we
+                        # don't want to check it
+                        "pull_request": ANY,
+                        "pullrequest": {
+                            "id": 1,
+                            "uid": ANY,
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "title": "PR from the feature branch",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "foo",
+                                "fullname": "foo bar",
+                                "url_path": "user/foo",
+                                "full_url": "http://localhost.localdomain/user/foo",
+                            },
+                            "assignee": None,
+                            "status": "Open",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": ["blue", "yellow"],
+                            "comments": [
+                                {
+                                    "id": 1,
+                                    "commit": None,
+                                    "tree": None,
+                                    "filename": None,
+                                    "line": None,
+                                    "comment": "**Metadata Update from "
+                                    "@pingou**:\n- Pull-request tagged "
+                                    "with: black",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": True,
+                                    "reactions": {},
+                                }
+                            ],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "url_path": "test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "tags": ["black"],
+                        "agent": "foo",
+                    },
+                ),
+            ):
+                output = self.app.post(
+                    "/test/pull-request/1/update",
+                    data=data,
+                    follow_redirects=True,
+                )
             self.assertEqual(output.status_code, 200)
             soup = BeautifulSoup(output.get_data(as_text=True), "html.parser")
             self.assertEqual(
@@ -2733,9 +4866,12 @@ index 0000000..2a552bb
             )
             self.assertEqual(output.status_code, 404)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.notify.send_email")
     def test_fork_project(self, send_email):
-        """ Test the fork_project endpoint. """
+        """Test the fork_project endpoint."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -2773,14 +4909,150 @@ index 0000000..2a552bb
 
             data = {"csrf_token": csrf_token}
 
+            with testing.mock_sends(
+                pagure_messages.ProjectForkedV1(
+                    topic="pagure.project.forked",
+                    body={
+                        "project": {
+                            "id": 4,
+                            "name": "test",
+                            "fullname": "forks/foo/test",
+                            "url_path": "fork/foo/test",
+                            "full_url": "http://localhost.localdomain/fork/foo/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "foo",
+                                "fullname": "foo bar",
+                                "url_path": "user/foo",
+                                "full_url": "http://localhost.localdomain/user/foo",
+                            },
+                            "access_users": {
+                                "owner": ["foo"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [],
+                            "milestones": {},
+                        },
+                        "agent": "foo",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/do_fork/test", data=data, follow_redirects=True
+                )
+                self.assertEqual(output.status_code, 200)
+
+    @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
+    def test_fork_project_non_master_default(self):
+        """Test the fork_project endpoint with a project whose default branch
+        is not master."""
+
+        tests.create_projects(self.session)
+        for folder in ["docs", "tickets", "requests", "repos"]:
+            tests.create_projects_git(
+                os.path.join(self.path, folder), bare=True
+            )
+        path = os.path.join(self.path, "repos", "test.git")
+        tests.add_content_git_repo(path)
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+
+        # Check before that the master branch is the default one - shown in the
+        # default page
+        output = self.app.get("/test")
+        self.assertEqual(output.status_code, 200)
+        output_text = output.get_data(as_text=True)
+        self.assertIn(
+            '<code class="py-1 px-2 font-weight-bold commit_branch">master</code><code',
+            output_text,
+        )
+
+        # Create the main branch with some content and make it the default branch
+        repo = pygit2.Repository(path)
+        branchname = "main"
+        repo.create_branch(branchname, repo.head.peel())
+        pagure.lib.git.git_set_ref_head(project=project, branch=branchname)
+
+        user = tests.FakeUser(username="foo")
+        with tests.user_set(self.app.application, user):
+            data = {"csrf_token": self.get_csrf()}
+
             output = self.app.post(
                 "/do_fork/test", data=data, follow_redirects=True
             )
             self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<code class="py-1 px-2 font-weight-bold commit_branch">main</code><code',
+                output_text,
+            )
+
+        output = self.app.get("/fork/foo/test")
+        self.assertEqual(output.status_code, 200)
+        output_text = output.get_data(as_text=True)
+        self.assertIn(
+            '<code class="py-1 px-2 font-weight-bold commit_branch">main</code><code',
+            output_text,
+        )
 
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull_branch_space(self, send_email):
-        """ Test the new_request_pull endpoint. """
+        """Test the new_request_pull endpoint."""
         send_email.return_value = True
 
         self.test_fork_project()
@@ -2809,9 +5081,12 @@ index 0000000..2a552bb
             output_text = output.get_data(as_text=True)
             self.assertIn("<p>Branch foo bar does not exist</p>", output_text)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull(self, send_email):
-        """ Test the new_request_pull endpoint. """
+        """Test the new_request_pull endpoint."""
         send_email.return_value = True
 
         self.test_fork_project()
@@ -2915,9 +5190,130 @@ More information</textarea>
                 "initial_comment": "Test Initial Comment",
             }
 
-            output = self.app.post(
-                "/test/diff/master..feature", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.PullRequestNewV1(
+                    topic="pagure.pull-request.new",
+                    body={
+                        "pullrequest": {
+                            "id": 2,
+                            "full_url": "http://localhost.localdomain/test/pull-request/2",
+                            "uid": ANY,
+                            "title": "foo bar PR",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "url_path": "test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": None,
+                            "status": "Open",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": "Test Initial Comment",
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [],
+                        },
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/diff/master..feature",
+                    data=data,
+                    follow_redirects=True,
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -2956,7 +5352,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull_filename_unicode(self, send_email):
-        """ Test the new_request_pull endpoint. """
+        """Test the new_request_pull endpoint."""
         send_email.return_value = True
 
         # Create the main project in the DB
@@ -3156,7 +5552,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull_req_sign_off_view(self, send_email):
-        """ Test the new_request_pull endpoint. """
+        """Test the new_request_pull endpoint."""
         send_email.return_value = True
 
         self.test_fork_project()
@@ -3214,7 +5610,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull_req_sign_off_submit(self, send_email):
-        """ Test the new_request_pull endpoint. """
+        """Test the new_request_pull endpoint."""
         send_email.return_value = True
 
         self.test_fork_project()
@@ -3303,7 +5699,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_request_pull_commit_start_stop(self, send_email):
-        """ Test the the commit start and stop of brand new PR. """
+        """Test the the commit start and stop of brand new PR."""
         send_email.return_value = True
 
         self.test_fork_project()
@@ -3370,7 +5766,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull_from_fork_branch(self, send_email):
-        """ Test creating a fork to fork PR. """
+        """Test creating a fork to fork PR."""
         send_email.return_value = True
 
         # Create main repo with some content
@@ -3456,7 +5852,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_new_request_pull_from_fork_fixing_ticket(self):
-        """ Test creating a fork to fork PR fixing a ticket. """
+        """Test creating a fork to fork PR fixing a ticket."""
         # Create main repo with some content
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -3586,7 +5982,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull_fork_to_fork_pr_disabled(self, send_email):
-        """ Test creating a fork to fork PR. """
+        """Test creating a fork to fork PR."""
         send_email.return_value = True
 
         self.test_fork_project()
@@ -3651,7 +6047,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull_fork_to_fork(self, send_email):
-        """ Test creating a fork to fork PR. """
+        """Test creating a fork to fork PR."""
         send_email.return_value = True
 
         self.test_fork_project()
@@ -3766,7 +6162,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull_fork_to_other_fork(self, send_email):
-        """ Test creating a PR from fork to a fork of the same family. """
+        """Test creating a PR from fork to a fork of the same family."""
         send_email.return_value = True
 
         self.test_fork_project()
@@ -3898,7 +6294,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull_fork_to_other_unrelated_fork(self, send_email):
-        """ Test creating a PR from  fork to fork that isn't from the same
+        """Test creating a PR from  fork to fork that isn't from the same
         family.
         """
         send_email.return_value = True
@@ -3986,12 +6382,12 @@ More information</textarea>
             self.assertIn(
                 "<p>fork/foo/test is not part of fork/ralph/test2's "
                 "family</p>",
-                output.get_data(as_text=True),
+                output.get_data(as_text=True).replace("&#x27;", "'"),
             )
 
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull_empty_repo(self, send_email):
-        """ Test the new_request_pull endpoint against an empty repo. """
+        """Test the new_request_pull endpoint against an empty repo."""
         send_email.return_value = True
 
         self.test_fork_project()
@@ -4046,7 +6442,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull_empty_fork(self, send_email):
-        """ Test the new_request_pull endpoint against an empty repo. """
+        """Test the new_request_pull endpoint against an empty repo."""
         send_email.return_value = True
 
         self.test_fork_project()
@@ -4084,9 +6480,12 @@ More information</textarea>
 
         shutil.rmtree(newpath)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.notify.send_email")
     def test_pull_request_add_comment(self, send_email):
-        """ Test the pull_request_add_comment endpoint. """
+        """Test the pull_request_add_comment endpoint."""
         send_email.return_value = True
 
         self.test_request_pull()
@@ -4114,11 +6513,151 @@ More information</textarea>
                 "csrf_token": csrf_token,
                 "comment": "This look alright but we can do better",
             }
-            output = self.app.post(
-                "/test/pull-request/1/comment",
-                data=data,
-                follow_redirects=True,
-            )
+            with testing.mock_sends(
+                pagure_messages.PullRequestCommentAddedV1(
+                    topic="pagure.pull-request.comment.added",
+                    body={
+                        "pullrequest": {
+                            "id": 1,
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "uid": ANY,
+                            "title": "PR from the feature branch",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": None,
+                            "status": "Open",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [
+                                {
+                                    "id": 1,
+                                    "commit": None,
+                                    "tree": None,
+                                    "filename": None,
+                                    "line": None,
+                                    "comment": "This look alright but we can do better",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": False,
+                                    "reactions": {},
+                                }
+                            ],
+                        },
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/pull-request/1/comment",
+                    data=data,
+                    follow_redirects=True,
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -4148,7 +6687,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_pull_request_drop_comment(self, send_email):
-        """ Test the pull_request_drop_comment endpoint. """
+        """Test the pull_request_drop_comment endpoint."""
         send_email.return_value = True
 
         self.test_pull_request_add_comment()
@@ -4245,9 +6784,12 @@ More information</textarea>
             )
             self.assertEqual(output.status_code, 404)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.notify.send_email")
     def test_pull_request_edit_comment(self, send_email):
-        """ Test the pull request edit comment endpoint """
+        """Test the pull request edit comment endpoint"""
         send_email.return_value = True
 
         self.test_request_pull()
@@ -4278,6 +6820,7 @@ More information</textarea>
                 "csrf_token": csrf_token,
                 "comment": "This look alright but we can do better",
             }
+
             output = self.app.post(
                 "/test/pull-request/1/comment",
                 data=data,
@@ -4317,11 +6860,196 @@ More information</textarea>
                 "csrf_token": csrf_token,
                 "update_comment": "This look alright but we can do better than this.",
             }
-            output = self.app.post(
-                "/test/pull-request/1/comment/1/edit",
-                data=data,
-                follow_redirects=True,
-            )
+            with testing.mock_sends(
+                pagure_messages.PullRequestCommentEditedV1(
+                    topic="pagure.pull-request.comment.edited",
+                    body={
+                        "pullrequest": {
+                            "id": 1,
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "uid": ANY,
+                            "title": "PR from the feature branch",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "url_path": "test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "assignee": None,
+                            "status": "Open",
+                            "commit_start": ANY,
+                            "commit_stop": ANY,
+                            "closed_by": None,
+                            "initial_comment": None,
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "comment": {
+                            "id": 1,
+                            "commit": None,
+                            "tree": None,
+                            "filename": None,
+                            "line": None,
+                            "comment": "This look alright but we can do better than this.",
+                            "parent": None,
+                            "date_created": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "edited_on": ANY,
+                            "editor": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "notification": False,
+                            "reactions": {},
+                        },
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/pull-request/1/comment/1/edit",
+                    data=data,
+                    follow_redirects=True,
+                )
             output_text = output.get_data(as_text=True)
             # Checking if the comment is updated in the main page
             self.assertIn(
@@ -4340,7 +7068,8 @@ More information</textarea>
             # Checking if Edited by User is there or not
             pattern = (
                 re.escape("<small>Edited ")
-                + "(just now|seconds ago)"
+                + '<span title="[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:'
+                + '[0-9]{2} UTC" data-toggle="tooltip">(just now|seconds ago)</span>'
                 + re.escape(" by pingou </small>")
             )
             self.assertIsNotNone(re.search(pattern, output_text))
@@ -4365,7 +7094,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_merge_request_pull_FF_w_merge_commit(self, send_email):
-        """ Test the merge_request_pull endpoint with a FF PR but with a
+        """Test the merge_request_pull endpoint with a FF PR but with a
         merge commit.
         """
         send_email.return_value = True
@@ -4400,7 +7129,7 @@ More information</textarea>
                 output_text,
             )
             self.assertIn(
-                'title="View file as of 2a552b">sources</a>', output_text
+                'title="View file as of 2a552bb">sources</a>', output_text
             )
 
             # Wrong project
@@ -4462,7 +7191,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_internal_endpoint_main_ahead(self, send_email):
-        """ Test the new_request_pull endpoint when the main repo is ahead
+        """Test the new_request_pull endpoint when the main repo is ahead
         of the fork.
         """
         send_email.return_value = True
@@ -4575,7 +7304,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_fork_edit_file(self, send_email):
-        """ Test the fork_edit file endpoint. """
+        """Test the fork_edit file endpoint."""
 
         send_email.return_value = True
 
@@ -4656,10 +7385,7 @@ More information</textarea>
             # Check fork-edit doesn't show for binary files
             output = self.app.get("/test/blob/master/f/test.jpg")
             self.assertEqual(output.status_code, 200)
-            self.assertNotIn(
-                "Fork and Edit\n                    </button>\n",
-                output.get_data(as_text=True),
-            )
+            self.assertNotIn(b"<html", output.data)
 
             # Check for edit panel
             output = self.app.post(
@@ -4717,8 +7443,8 @@ More information</textarea>
             )
             self.assertEqual(output.status_code, 400)
             self.assertIn(
-                "<p>Cannot edit binary files</p>",
-                output.get_data(as_text=True),
+                b"<p>Cannot edit binary files</p>",
+                output.data,
             )
 
         # Check fork-edit shows when user is not logged in
@@ -4744,14 +7470,11 @@ More information</textarea>
             # Check fork-edit doesn't show for binary
             output = self.app.get("/test/blob/master/f/test.jpg")
             self.assertEqual(output.status_code, 200)
-            self.assertNotIn(
-                "Edit in your fork\n                    </button>\n",
-                output.get_data(as_text=True),
-            )
+            self.assertNotIn(b"<html", output.data)
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_fork_edit_file_namespace(self):
-        """ Test the fork_edit file endpoint on a namespaced project. """
+        """Test the fork_edit file endpoint on a namespaced project."""
 
         tests.create_projects(self.session)
         for folder in ["docs", "tickets", "requests", "repos"]:
@@ -4845,10 +7568,7 @@ More information</textarea>
                 "/somenamespace/test3/blob/master/f/test.jpg"
             )
             self.assertEqual(output.status_code, 200)
-            self.assertNotIn(
-                "Fork and Edit\n                    </button>\n",
-                output.get_data(as_text=True),
-            )
+            self.assertNotIn(b"<html", output.data)
 
             # Check for edit panel
             output = self.app.post(
@@ -4911,7 +7631,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_fork_without_main_repo(self, send_email):
-        """ Test the fork without the main repo. """
+        """Test the fork without the main repo."""
         send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -5016,7 +7736,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_add_reaction(self, send_email):
-        """ Test the request_pull endpoint. """
+        """Test the request_pull endpoint."""
         send_email.return_value = True
 
         self._set_up_for_reaction_test()
@@ -5048,7 +7768,7 @@ More information</textarea>
 
     @patch("pagure.lib.notify.send_email")
     def test_add_reaction_unauthenticated(self, send_email):
-        """ Test the request_pull endpoint. """
+        """Test the request_pull endpoint."""
         send_email.return_value = True
 
         self._set_up_for_reaction_test()
@@ -5072,11 +7792,11 @@ More information</textarea>
 
 
 class TestTicketAccessEditPRMetadata(tests.Modeltests):
-    """ Tests that people with ticket access on a project can edit the
-    meta-data of a PR """
+    """Tests that people with ticket access on a project can edit the
+    meta-data of a PR"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(TestTicketAccessEditPRMetadata, self).setUp()
         tests.create_projects(self.session)
         tests.create_projects_git(
@@ -5099,8 +7819,8 @@ class TestTicketAccessEditPRMetadata(tests.Modeltests):
         self.assertEqual(msg, "User added")
 
     def test_unauth_cannot_view_edit_metadata_ui(self):
-        """ Test that unauthenticated users cannot view the edit the
-        metadata fields in the UI. """
+        """Test that unauthenticated users cannot view the edit the
+        metadata fields in the UI."""
 
         output = self.app.get("/test/pull-request/1")
         self.assertEqual(output.status_code, 200)
@@ -5123,8 +7843,8 @@ class TestTicketAccessEditPRMetadata(tests.Modeltests):
         )
 
     def test_admin_can_view_edit_metadata_ui(self):
-        """ Test that admin users can view the edit the metadata fields in
-        the UI. """
+        """Test that admin users can view the edit the metadata fields in
+        the UI."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -5149,7 +7869,7 @@ class TestTicketAccessEditPRMetadata(tests.Modeltests):
             )
 
     def test_admin_can_edit_metadata_ui(self):
-        """ Test that admin users can edit the metadata in the UI. """
+        """Test that admin users can edit the metadata in the UI."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -5182,8 +7902,8 @@ class TestTicketAccessEditPRMetadata(tests.Modeltests):
             )
 
     def test_ticket_can_view_edit_metadata_ui(self):
-        """ Test that users with ticket access can view the edit the
-        metadata fields in the UI. """
+        """Test that users with ticket access can view the edit the
+        metadata fields in the UI."""
 
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
@@ -5209,8 +7929,8 @@ class TestTicketAccessEditPRMetadata(tests.Modeltests):
             )
 
     def test_ticket_can_edit_metadata_ui(self):
-        """ Test that users with ticket access can edit the metadata in the
-        UI. """
+        """Test that users with ticket access can edit the metadata in the
+        UI."""
 
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
diff --git a/tests/test_pagure_flask_ui_groups.py b/tests/test_pagure_flask_ui_groups.py
index f73845f..3531a40 100644
--- a/tests/test_pagure_flask_ui_groups.py
+++ b/tests/test_pagure_flask_ui_groups.py
@@ -16,7 +16,9 @@ import sys
 import os
 
 import json
-from mock import patch
+import pagure_messages
+from fedora_messaging import api, testing
+from mock import ANY, patch
 
 sys.path.insert(
     0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
@@ -27,10 +29,10 @@ import tests
 
 
 class PagureFlaskGroupstests(tests.Modeltests):
-    """ Tests for flask groups controller of pagure """
+    """Tests for flask groups controller of pagure"""
 
     def test_group_lists(self):
-        """ Test the group_lists endpoint. """
+        """Test the group_lists endpoint."""
         output = self.app.get("/groups")
         self.assertIn(
             '<h3 class="font-weight-bold">\n'
@@ -39,7 +41,7 @@ class PagureFlaskGroupstests(tests.Modeltests):
         )
 
     def test_add_group(self):
-        """ Test the add_group endpoint. """
+        """Test the add_group endpoint."""
         output = self.app.get("/group/add")
         self.assertEqual(output.status_code, 302)
 
@@ -161,8 +163,11 @@ class PagureFlaskGroupstests(tests.Modeltests):
                 output.get_data(as_text=True),
             )
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     def test_edit_group(self):
-        """ Test the edit_group endpoint. """
+        """Test the edit_group endpoint."""
 
         output = self.app.get("/group/test_group/edit")
         self.assertEqual(output.status_code, 302)
@@ -197,7 +202,7 @@ class PagureFlaskGroupstests(tests.Modeltests):
             )
             self.assertIn(
                 '<strong><label for="description">Description'
-                "</label></strong>",
+                "</label> </strong>",
                 output.get_data(as_text=True),
             )
 
@@ -228,7 +233,7 @@ class PagureFlaskGroupstests(tests.Modeltests):
             )
             self.assertIn(
                 '<strong><label for="description">Description'
-                "</label></strong>",
+                "</label> </strong>",
                 output.get_data(as_text=True),
             )
 
@@ -244,7 +249,7 @@ class PagureFlaskGroupstests(tests.Modeltests):
                 output.get_data(as_text=True),
             )
             self.assertIn(
-                "You are not " "allowed to edit this group",
+                "You are not allowed to edit this group",
                 output.get_data(as_text=True),
             )
             self.assertIn(
@@ -263,10 +268,35 @@ class PagureFlaskGroupstests(tests.Modeltests):
                 "<p>Group not found</p>", output.get_data(as_text=True)
             )
 
-            output = self.app.post(
-                "/group/test_group/edit", data=data, follow_redirects=True
-            )
-            self.assertEqual(output.status_code, 200)
+            # All good
+            with testing.mock_sends(
+                pagure_messages.GroupEditV1(
+                    topic="pagure.group.edit",
+                    body={
+                        "group": {
+                            "name": "test_group",
+                            "display_name": "Test Group edited",
+                            "description": "This is a group for the tests edited",
+                            "group_type": "user",
+                            "creator": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "date_created": ANY,
+                            "members": ["pingou"],
+                            "full_url": "http://localhost.localdomain/group/test_group",
+                        },
+                        "fields": ["display_name", "description"],
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/group/test_group/edit", data=data, follow_redirects=True
+                )
+                self.assertEqual(output.status_code, 200)
             self.assertIn(
                 "<title>Group test_group - Pagure</title>",
                 output.get_data(as_text=True),
@@ -281,7 +311,7 @@ class PagureFlaskGroupstests(tests.Modeltests):
             )
 
     def test_give_group(self):
-        """ Test the give_group endpoint. """
+        """Test the give_group endpoint."""
 
         output = self.app.post("/group/test_group/give")
         self.assertEqual(output.status_code, 302)
@@ -363,7 +393,7 @@ class PagureFlaskGroupstests(tests.Modeltests):
             )
 
     def test_group_delete(self):
-        """ Test the group_delete endpoint. """
+        """Test the group_delete endpoint."""
         output = self.app.post("/group/foo/delete")
         self.assertEqual(output.status_code, 302)
 
@@ -457,7 +487,7 @@ class PagureFlaskGroupstests(tests.Modeltests):
             )
 
     def test_view_group(self):
-        """ Test the view_group endpoint. """
+        """Test the view_group endpoint."""
         output = self.app.get("/group/foo")
         self.assertEqual(output.status_code, 404)
 
@@ -541,7 +571,7 @@ class PagureFlaskGroupstests(tests.Modeltests):
             )
 
     def test_group_user_delete(self):
-        """ Test the group_user_delete endpoint. """
+        """Test the group_user_delete endpoint."""
         output = self.app.post("/group/foo/bar/delete")
         self.assertEqual(output.status_code, 302)
 
diff --git a/tests/test_pagure_flask_ui_issue_pr_link.py b/tests/test_pagure_flask_ui_issue_pr_link.py
index 8a5bdf5..5379771 100644
--- a/tests/test_pagure_flask_ui_issue_pr_link.py
+++ b/tests/test_pagure_flask_ui_issue_pr_link.py
@@ -34,12 +34,12 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureFlaskPrIssueLinkTest(tests.Modeltests):
-    """ Tests pagure when linking PRs to tickets """
+    """Tests pagure when linking PRs to tickets"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskPrIssueLinkTest, self).setUp()
 
         tests.create_projects(self.session)
@@ -136,8 +136,7 @@ class PagureFlaskPrIssueLinkTest(tests.Modeltests):
         self.assertEqual(request.id, 3)
 
     def test_ticket_no_link(self):
-        """ Test that no Related PR(s) block is showing in the issue page.
-        """
+        """Test that no Related PR(s) block is showing in the issue page."""
         output = self.app.get("/test/issue/1")
         self.assertEqual(output.status_code, 200)
         self.assertNotIn(
@@ -145,15 +144,13 @@ class PagureFlaskPrIssueLinkTest(tests.Modeltests):
         )
 
     def test_ticket_link(self):
-        """ Test that a Related PR(s) block is showing in the issue page.
-        """
+        """Test that a Related PR(s) block is showing in the issue page."""
         output = self.app.get("/test/issue/2")
         self.assertEqual(output.status_code, 200)
         self.assertIn("Related Pull Requests", output.get_data(as_text=True))
 
     def test_pr_link_issue_list(self):
-        """ Test that the related PR(s) shows in the page listing issues
-        """
+        """Test that the related PR(s) shows in the page listing issues"""
         output = self.app.get("/test/issues")
         self.assertEqual(output.status_code, 200)
         self.assertIn(
diff --git a/tests/test_pagure_flask_ui_issues.py b/tests/test_pagure_flask_ui_issues.py
index 6aba6c6..0af9975 100644
--- a/tests/test_pagure_flask_ui_issues.py
+++ b/tests/test_pagure_flask_ui_issues.py
@@ -21,15 +21,17 @@ try:
     import pyclamd
 except ImportError:
     pyclamd = None
+import pagure_messages
 import six
 import tempfile
 import re
 from datetime import datetime, timedelta
+from fedora_messaging import testing
 from six.moves.urllib.parse import urlparse, parse_qs
 
 import pygit2
 from bs4 import BeautifulSoup
-from mock import patch, MagicMock
+from mock import ANY, patch, MagicMock
 
 sys.path.insert(
     0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
@@ -40,7 +42,7 @@ import tests
 
 
 class PagureFlaskIssuestests(tests.Modeltests):
-    """ Tests for flask issues controller of pagure """
+    """Tests for flask issues controller of pagure"""
 
     @patch.dict(
         "pagure.config.config", {"ENABLE_TICKETS_NAMESPACE": ["foobar"]}
@@ -48,7 +50,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_new_issue_wrong_namespace(self):
-        """ Test the new_issue endpoint. """
+        """Test the new_issue endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -99,7 +101,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_new_issue(self, p_send_email, p_ugt):
-        """ Test the new_issue endpoint. """
+        """Test the new_issue endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -202,7 +204,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_new_issue_customized(self):
-        """ Test the new_issue endpoint. """
+        """Test the new_issue endpoint."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
         tests.create_projects_git(
@@ -231,7 +233,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_new_issue_w_file(self, p_send_email, p_ugt):
-        """ Test the new_issue endpoint with a file. """
+        """Test the new_issue endpoint with a file."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -290,7 +292,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_new_issue_w_file_no_issue_tracker(self, p_send_email, p_ugt):
-        """ Test the new_issue endpoint with a file. """
+        """Test the new_issue endpoint with a file."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -327,7 +329,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_new_issue_w_file_namespace(self, p_send_email, p_ugt):
-        """ Test the new_issue endpoint with a file. """
+        """Test the new_issue endpoint with a file."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -389,7 +391,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_new_issue_w_files(self, p_send_email, p_ugt):
-        """ Test the new_issue endpoint with two files. """
+        """Test the new_issue endpoint with two files."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -468,7 +470,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_new_issue_w_files_namespace(self, p_send_email, p_ugt):
-        """ Test the new_issue endpoint with two files. """
+        """Test the new_issue endpoint with two files."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -538,8 +540,8 @@ class PagureFlaskIssuestests(tests.Modeltests):
             )
 
     def test_new_issue_metadata_user(self):
-        """ Test the new_issue endpoint when the user has access to the
-        project. """
+        """Test the new_issue endpoint when the user has access to the
+        project."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -561,8 +563,8 @@ class PagureFlaskIssuestests(tests.Modeltests):
             self.assertIn("<strong>Assignee</strong>", output_text)
 
     def test_new_issue_metadata_not_user(self):
-        """ Test the new_issue endpoint when the user does not have access
-        to the project. """
+        """Test the new_issue endpoint when the user does not have access
+        to the project."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -586,8 +588,8 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_new_issue_with_metadata(self):
-        """ Test the new_issue endpoint when the user has access to the
-        project. """
+        """Test the new_issue endpoint when the user has access to the
+        project."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -656,7 +658,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_new_issue_with_metadata_not_user(self):
-        """ Test the new_issue endpoint when the user does not have access
+        """Test the new_issue endpoint when the user does not have access
         to the project but still tries to.
         """
 
@@ -735,7 +737,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_view_issues_wrong_namespace(self):
-        """ Test the view_issues endpoint. """
+        """Test the view_issues endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -764,7 +766,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issues(self, p_send_email, p_ugt):
-        """ Test the view_issues endpoint. """
+        """Test the view_issues endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1220,7 +1222,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_search_issues_unicode(self, p_send_email, p_ugt):
-        """ Test the view_issues endpoint filtering for an unicode char. """
+        """Test the view_issues endpoint filtering for an unicode char."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1274,8 +1276,8 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue_inconsistent_milestone(self, p_send_email, p_ugt):
-        """ Test the view_issue endpoint when the milestone keys are
-        inconsistent with the milestones of the project. """
+        """Test the view_issue endpoint when the milestone keys are
+        inconsistent with the milestones of the project."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1321,7 +1323,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue(self, p_send_email, p_ugt):
-        """ Test the view_issue endpoint. """
+        """Test the view_issue endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1460,7 +1462,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_view_issue_author(self):
-        """ Test the view_issue endpoint when you're the author. """
+        """Test the view_issue endpoint when you're the author."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -1539,7 +1541,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue_user_ticket(self, p_send_email, p_ugt):
-        """ Test the view_issue endpoint. """
+        """Test the view_issue endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1616,7 +1618,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue_custom_field_user_ticket(self, p_send_email, p_ugt):
-        """ Test the view_issue endpoint. """
+        """Test the view_issue endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1745,7 +1747,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue_non_ascii_milestone(self, p_send_email, p_ugt):
-        """ Test the view_issue endpoint with non-ascii milestone. """
+        """Test the view_issue endpoint with non-ascii milestone."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1809,8 +1811,8 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue_list_no_data(self, p_send_email, p_ugt):
-        """ Test the view_issue endpoint when the issue has a custom field
-        of type list with no data attached. """
+        """Test the view_issue endpoint when the issue has a custom field
+        of type list with no data attached."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1864,7 +1866,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_issue_wrong_namespace(self):
-        """ Test the update_issue endpoint. """
+        """Test the update_issue endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -1934,10 +1936,13 @@ class PagureFlaskIssuestests(tests.Modeltests):
                 " <p>No issue tracker found for this project</p>", output_text
             )
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_update_issue_add_tags(self, p_send_email, p_ugt):
-        """ Test the update_issue endpoint. """
+        """Test the update_issue endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1991,9 +1996,87 @@ class PagureFlaskIssuestests(tests.Modeltests):
 
             # Add two tags to the issue
             data = {"csrf_token": csrf_token, "tag": "red,green"}
-            output = self.app.post(
-                "/test/issue/1/update", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.IssueTagAddedV1(
+                    topic="pagure.issue.tag.added",
+                    body={
+                        "issue": {
+                            "id": 1,
+                            "title": "Test issue",
+                            "content": "We should work on this",
+                            "full_url": "http://localhost.localdomain/test/issue/1",
+                            "status": "Open",
+                            "close_status": None,
+                            "date_created": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "private": False,
+                            "tags": ["green", "red"],
+                            "depends": [],
+                            "blocks": [],
+                            "assignee": None,
+                            "priority": None,
+                            "milestone": None,
+                            "custom_fields": [],
+                            "closed_by": None,
+                            "related_prs": [],
+                            "comments": [],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "tags": ["green", "red"],
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/issue/1/update", data=data, follow_redirects=True
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -2014,10 +2097,141 @@ class PagureFlaskIssuestests(tests.Modeltests):
         self.assertEqual([tag.tag for tag in tags], ["green", "red"])
         self.assertEqual(repo.issues[0].tags_text, [])
 
+        # Drop one of the two tags
+        user = tests.FakeUser()
+        user.username = "pingou"
+        with tests.user_set(self.app.application, user):
+
+            # Add two tags to the issue
+            data = {"csrf_token": csrf_token, "tag": "green"}
+            with testing.mock_sends(
+                pagure_messages.IssueTagRemovedV1(
+                    topic="pagure.issue.tag.removed",
+                    body={
+                        "issue": {
+                            "id": 1,
+                            "title": "Test issue",
+                            "content": "We should work on this",
+                            "full_url": "http://localhost.localdomain/test/issue/1",
+                            "status": "Open",
+                            "close_status": None,
+                            "date_created": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "private": False,
+                            "tags": ["green"],
+                            "depends": [],
+                            "blocks": [],
+                            "assignee": None,
+                            "priority": None,
+                            "milestone": None,
+                            "custom_fields": [],
+                            "closed_by": None,
+                            "related_prs": [],
+                            "comments": [
+                                {
+                                    "id": 1,
+                                    "comment": "**Metadata Update from "
+                                    "@pingou**:\n- Issue tagged with: green, "
+                                    "red",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": True,
+                                    "reactions": {},
+                                }
+                            ],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "tags": ["red"],
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/issue/1/update", data=data, follow_redirects=True
+                )
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                "<title>Issue #1: Test issue - test - Pagure</title>",
+                output_text,
+            )
+            self.assertIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text,
+            )
+            self.assertIn(
+                "<br>\n- Issue tagged with: green, red</p>", output_text
+            )
+            self.assertIn(
+                "<br>\n- Issue <strong>un</strong>tagged with: red</p>",
+                output_text,
+            )
+
+        # After update, list tags
+        tags = pagure.lib.query.get_tags_of_project(self.session, repo)
+        self.assertEqual([tag.tag for tag in tags], ["green", "red"])
+        self.assertEqual(repo.issues[0].tags_text, [])
+
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_update_issue(self, p_send_email, p_ugt):
-        """ Test the update_issue endpoint. """
+        """Test the update_issue endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -2347,10 +2561,13 @@ class PagureFlaskIssuestests(tests.Modeltests):
             output = self.app.post("/test/issue/1/update", data=data)
             self.assertEqual(output.status_code, 404)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_update_issue_drop_comment(self, p_send_email, p_ugt):
-        """ Test droping comment via the update_issue endpoint. """
+        """Test droping comment via the update_issue endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -2392,9 +2609,103 @@ class PagureFlaskIssuestests(tests.Modeltests):
                 "csrf_token": csrf_token,
                 "comment": "Woohoo a second comment!",
             }
-            output = self.app.post(
-                "/test/issue/1/update", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.IssueCommentAddedV1(
+                    topic="pagure.issue.comment.added",
+                    body={
+                        "issue": {
+                            "id": 1,
+                            "title": "Test issue",
+                            "content": "We should work on this",
+                            "full_url": "http://localhost.localdomain/test/issue/1",
+                            "status": "Open",
+                            "close_status": None,
+                            "date_created": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "private": False,
+                            "tags": [],
+                            "depends": [],
+                            "blocks": [],
+                            "assignee": None,
+                            "priority": None,
+                            "milestone": None,
+                            "custom_fields": [],
+                            "closed_by": None,
+                            "related_prs": [],
+                            "comments": [
+                                {
+                                    "id": 1,
+                                    "comment": "Woohoo a second comment!",
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "edited_on": None,
+                                    "editor": None,
+                                    "notification": False,
+                                    "reactions": {},
+                                }
+                            ],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/issue/1/update", data=data, follow_redirects=True
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -2461,10 +2772,13 @@ class PagureFlaskIssuestests(tests.Modeltests):
         issue = pagure.lib.query.search_issues(self.session, repo, issueid=1)
         self.assertEqual(len(issue.comments), 0)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_update_issue_depend(self, p_send_email, p_ugt):
-        """ Test adding dependency via the update_issue endpoint. """
+        """Test adding dependency via the update_issue endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -2514,9 +2828,87 @@ class PagureFlaskIssuestests(tests.Modeltests):
 
             # Add a dependent ticket
             data = {"csrf_token": csrf_token, "depending": "2"}
-            output = self.app.post(
-                "/test/issue/1/update", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.IssueDependencyAddedV1(
+                    topic="pagure.issue.dependency.added",
+                    body={
+                        "issue": {
+                            "id": 2,
+                            "title": "Test issue #2",
+                            "full_url": "http://localhost.localdomain/test/issue/2",
+                            "content": "We should work on this again",
+                            "status": "Open",
+                            "close_status": None,
+                            "date_created": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "foo",
+                                "fullname": "foo bar",
+                                "url_path": "user/foo",
+                                "full_url": "http://localhost.localdomain/user/foo",
+                            },
+                            "private": False,
+                            "tags": [],
+                            "depends": [],
+                            "blocks": ["1"],
+                            "assignee": None,
+                            "priority": None,
+                            "milestone": None,
+                            "custom_fields": [],
+                            "closed_by": None,
+                            "related_prs": [],
+                            "comments": [],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "url_path": "test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "added_dependency": 1,
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/issue/1/update", data=data, follow_redirects=True
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -2555,8 +2947,8 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_issue_block_closed(self):
-        """ Test how blocked issue shows in the UI when the blocking ticket
-        is open and closed. """
+        """Test how blocked issue shows in the UI when the blocking ticket
+        is open and closed."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -2670,10 +3062,13 @@ class PagureFlaskIssuestests(tests.Modeltests):
         self.assertEqual(issue.depending_text, [])
         self.assertEqual(issue.blocking_text, [2])
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_update_issue_block(self, p_send_email, p_ugt):
-        """ Test adding blocked issue via the update_issue endpoint. """
+        """Test adding blocked issue via the update_issue endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -2682,15 +3077,92 @@ class PagureFlaskIssuestests(tests.Modeltests):
 
         # Create issues to play with
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
-        msg = pagure.lib.query.new_issue(
-            session=self.session,
-            repo=repo,
-            title="Test issue",
-            content="We should work on this",
-            user="pingou",
-        )
-        self.session.commit()
-        self.assertEqual(msg.title, "Test issue")
+        with testing.mock_sends(
+            pagure_messages.IssueNewV1(
+                topic="pagure.issue.new",
+                body={
+                    "issue": {
+                        "id": 1,
+                        "title": "Test issue",
+                        "content": "We should work on this",
+                        "full_url": "http://localhost.localdomain/test/issue/1",
+                        "status": "Open",
+                        "close_status": None,
+                        "date_created": ANY,
+                        "last_updated": ANY,
+                        "closed_at": None,
+                        "user": {
+                            "name": "pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                        },
+                        "private": False,
+                        "tags": [],
+                        "depends": [],
+                        "blocks": [],
+                        "assignee": None,
+                        "priority": None,
+                        "milestone": None,
+                        "custom_fields": [],
+                        "closed_by": None,
+                        "related_prs": [],
+                        "comments": [],
+                    },
+                    "project": {
+                        "id": 1,
+                        "name": "test",
+                        "fullname": "test",
+                        "full_url": "http://localhost.localdomain/test",
+                        "url_path": "test",
+                        "description": "test project #1",
+                        "namespace": None,
+                        "parent": None,
+                        "date_created": ANY,
+                        "date_modified": ANY,
+                        "user": {
+                            "name": "pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                        },
+                        "access_users": {
+                            "owner": ["pingou"],
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "access_groups": {
+                            "admin": [],
+                            "commit": [],
+                            "collaborator": [],
+                            "ticket": [],
+                        },
+                        "tags": [],
+                        "priorities": {},
+                        "custom_keys": [],
+                        "close_status": [
+                            "Invalid",
+                            "Insufficient data",
+                            "Fixed",
+                            "Duplicate",
+                        ],
+                        "milestones": {},
+                    },
+                    "agent": "pingou",
+                },
+            )
+        ):
+            msg = pagure.lib.query.new_issue(
+                session=self.session,
+                repo=repo,
+                title="Test issue",
+                content="We should work on this",
+                user="pingou",
+            )
+            self.session.commit()
+            self.assertEqual(msg.title, "Test issue")
 
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         msg = pagure.lib.query.new_issue(
@@ -2755,9 +3227,87 @@ class PagureFlaskIssuestests(tests.Modeltests):
 
             # Add a dependent ticket
             data = {"csrf_token": csrf_token, "blocking": "2"}
-            output = self.app.post(
-                "/test/issue/1/update", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.IssueDependencyAddedV1(
+                    topic="pagure.issue.dependency.added",
+                    body={
+                        "issue": {
+                            "id": 1,
+                            "title": "Test issue",
+                            "full_url": "http://localhost.localdomain/test/issue/1",
+                            "content": "We should work on this",
+                            "status": "Open",
+                            "close_status": None,
+                            "date_created": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "private": False,
+                            "tags": [],
+                            "depends": [],
+                            "blocks": ["2"],
+                            "assignee": None,
+                            "priority": None,
+                            "milestone": None,
+                            "custom_fields": [],
+                            "closed_by": None,
+                            "related_prs": [],
+                            "comments": [],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "added_dependency": 2,
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/issue/1/update", data=data, follow_redirects=True
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -2794,10 +3344,131 @@ class PagureFlaskIssuestests(tests.Modeltests):
         self.assertEqual(issue.depending_text, [])
         self.assertEqual(issue.blocking_text, [2])
 
+        # Now remove the dependency and check everything is recorded
+
+        user = tests.FakeUser()
+        user.username = "pingou"
+        with tests.user_set(self.app.application, user):
+            output = self.app.get("/test/issue/1")
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                "<title>Issue #1: Test issue - test - Pagure</title>",
+                output_text,
+            )
+            self.assertIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text,
+            )
+
+            csrf_token = self.get_csrf(output=output)
+
+            # Add a dependent ticket
+            data = {"csrf_token": csrf_token}
+            with testing.mock_sends(
+                pagure_messages.IssueDependencyRemovedV1(
+                    topic="pagure.issue.dependency.removed",
+                    body={
+                        "issue": {
+                            "id": 2,
+                            "title": "Test issue #2",
+                            "full_url": "http://localhost.localdomain/test/issue/2",
+                            "content": "We should work on this again",
+                            "status": "Open",
+                            "close_status": None,
+                            "date_created": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "foo",
+                                "fullname": "foo bar",
+                                "url_path": "user/foo",
+                                "full_url": "http://localhost.localdomain/user/foo",
+                            },
+                            "private": False,
+                            "tags": [],
+                            "depends": [],
+                            "blocks": [],
+                            "assignee": None,
+                            "priority": None,
+                            "milestone": None,
+                            "custom_fields": [],
+                            "closed_by": None,
+                            "related_prs": [],
+                            "comments": [],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "removed_dependency": [1],
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/issue/1/update", data=data, follow_redirects=True
+                )
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                "<title>Issue #1: Test issue - test - Pagure</title>",
+                output_text,
+            )
+            self.assertIn(
+                '<a class="btn btn-outline-secondary btn-sm border-0"'
+                ' href="/test/issue/1/edit" title="Edit this issue">',
+                output_text,
+            )
+
+        self.session.commit()
+        repo = pagure.lib.query.get_authorized_project(self.session, "test")
+        issue = pagure.lib.query.search_issues(self.session, repo, issueid=1)
+        self.assertEqual(issue.depending_text, [])
+        self.assertEqual(issue.blocking_text, [])
+
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_upload_issue(self, p_send_email, p_ugt):
-        """ Test the upload_issue endpoint. """
+        """Test the upload_issue endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -2894,7 +3565,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_upload_issue_virus(self, p_send_email, p_ugt):
-        """ Test the upload_issue endpoint. """
+        """Test the upload_issue endpoint."""
         if not pyclamd:
             raise SkipTest()
         p_send_email.return_value = True
@@ -2948,7 +3619,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_upload_issue_two_files(self, p_send_email, p_ugt):
-        """ Test the upload_issue endpoint with two files. """
+        """Test the upload_issue endpoint with two files."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3014,7 +3685,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
             self.assertDictEqual(json_data, exp)
 
     def test_view_issue_raw_file_empty(self):
-        """ Test the view_issue_raw_file endpoint. """
+        """Test the view_issue_raw_file endpoint."""
         # Create the project and git repos
         tests.create_projects(self.session)
         tests.create_projects_git(
@@ -3056,7 +3727,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
         self.assertEqual(output.status_code, 404)
 
     def test_view_issue_raw_file(self):
-        """ Test the view_issue_raw_file endpoint. """
+        """Test the view_issue_raw_file endpoint."""
         # Create the issue and upload to it
         self.test_upload_issue()
 
@@ -3092,10 +3763,13 @@ class PagureFlaskIssuestests(tests.Modeltests):
         output = self.app.get("/test" + url)
         self.assertEqual(output.status_code, 404)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_edit_issue(self, p_send_email, p_ugt):
-        """ Test the edit_issue endpoint. """
+        """Test the edit_issue endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3168,9 +3842,87 @@ class PagureFlaskIssuestests(tests.Modeltests):
             self.assertEqual(output_text.count("Not a valid choice"), 0)
 
             data["csrf_token"] = csrf_token
-            output = self.app.post(
-                "/test/issue/1/edit", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.IssueEditV1(
+                    topic="pagure.issue.edit",
+                    body={
+                        "issue": {
+                            "id": 1,
+                            "title": "Test issue #1",
+                            "full_url": "http://localhost.localdomain/test/issue/1",
+                            "content": "We should work on this!",
+                            "status": "Open",
+                            "close_status": None,
+                            "date_created": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "private": False,
+                            "tags": [],
+                            "depends": [],
+                            "blocks": [],
+                            "assignee": None,
+                            "priority": None,
+                            "milestone": None,
+                            "custom_fields": [],
+                            "closed_by": None,
+                            "related_prs": [],
+                            "comments": [],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "url_path": "test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "fields": ["content", "title"],
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/issue/1/edit", data=data, follow_redirects=True
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -3203,7 +3955,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_edit_issue_no_change(self):
-        """ Test the edit_issue endpoint. """
+        """Test the edit_issue endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -3260,13 +4012,13 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_edit_tag_issue_disabled(self):
-        """ Test the edit_tag endpoint when issues are disabled. """
+        """Test the edit_tag endpoint when issues are disabled."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
 
         # disable issue tracker
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
-        repo.settings = {"issue_tracker": False}
+        repo.settings = {"issue_tracker": False, "fedmsg_notifications": True}
         self.session.add(repo)
         self.session.commit()
 
@@ -3325,10 +4077,13 @@ class PagureFlaskIssuestests(tests.Modeltests):
         tags = pagure.lib.query.get_tags_of_project(self.session, repo)
         self.assertEqual([tag.tag for tag in tags], ["tag1"])
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_edit_tag(self, p_send_email, p_ugt):
-        """ Test the edit_tag endpoint. """
+        """Test the edit_tag endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3405,17 +4160,72 @@ class PagureFlaskIssuestests(tests.Modeltests):
             )
 
             data["csrf_token"] = csrf_token
-            output = self.app.post(
-                "/test/tag/tag1/edit", data=data, follow_redirects=True
-            )
-            self.assertEqual(output.status_code, 200)
-            output_text = output.get_data(as_text=True)
-            self.assertIn("Settings - test - Pagure", output_text)
-            self.assertIn(
-                ""
-                "Edited tag: tag1()[DeepSkyBlue] to tag2(lorem ipsum)[DeepSkyBlue]",
-                output_text,
-            )
+            with testing.mock_sends(
+                pagure_messages.ProjectTagEditedV1(
+                    topic="pagure.project.tag.edited",
+                    body={
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "old_tag": "tag2",
+                        "old_tag_description": "",
+                        "old_tag_color": "DeepSkyBlue",
+                        "new_tag": "tag2",
+                        "new_tag_description": "lorem ipsum",
+                        "new_tag_color": "DeepSkyBlue",
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/tag/tag1/edit", data=data, follow_redirects=True
+                )
+                self.assertEqual(output.status_code, 200)
+                output_text = output.get_data(as_text=True)
+                self.assertIn("Settings - test - Pagure", output_text)
+                self.assertIn(
+                    ""
+                    "Edited tag: tag1()[DeepSkyBlue] to tag2(lorem ipsum)[DeepSkyBlue]",
+                    output_text,
+                )
 
             # update tag with empty description
             data["tag_description"] = ""
@@ -3436,10 +4246,13 @@ class PagureFlaskIssuestests(tests.Modeltests):
         tags = pagure.lib.query.get_tags_of_project(self.session, repo)
         self.assertEqual([tag.tag for tag in tags], ["tag2"])
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_remove_tag_issue_disabled(self):
-        """ Test the remove_tag endpoint. """
+        """Test the remove_tag endpoint."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
 
@@ -3477,9 +4290,59 @@ class PagureFlaskIssuestests(tests.Modeltests):
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             data = {"tag": "tag1", "csrf_token": self.get_csrf()}
-            output = self.app.post(
-                "/test/droptag/", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.ProjectTagRemovedV1(
+                    topic="pagure.project.tag.removed",
+                    body={
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "tags": ["tag1"],
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/droptag/", data=data, follow_redirects=True
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -3492,7 +4355,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_remove_tag(self, p_send_email, p_ugt):
-        """ Test the remove_tag endpoint. """
+        """Test the remove_tag endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3584,10 +4447,13 @@ class PagureFlaskIssuestests(tests.Modeltests):
             )
             self.assertIn("" "Tag: tag1 has been deleted", output_text)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_delete_issue(self, p_send_email, p_ugt):
-        """ Test the delete_issue endpoint. """
+        """Test the delete_issue endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3644,9 +4510,86 @@ class PagureFlaskIssuestests(tests.Modeltests):
             )
 
             data["csrf_token"] = csrf_token
-            output = self.app.post(
-                "/test/issue/1/drop", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.IssueDropV1(
+                    topic="pagure.issue.drop",
+                    body={
+                        "issue": {
+                            "id": 1,
+                            "title": "Test issue",
+                            "full_url": "http://localhost.localdomain/test/issue/1",
+                            "content": "We should work on this",
+                            "status": "Open",
+                            "close_status": None,
+                            "date_created": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "private": False,
+                            "tags": [],
+                            "depends": [],
+                            "blocks": [],
+                            "assignee": None,
+                            "priority": None,
+                            "milestone": None,
+                            "custom_fields": [],
+                            "closed_by": None,
+                            "related_prs": [],
+                            "comments": [],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/issue/1/drop", data=data, follow_redirects=True
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn("<title>Issues - test - Pagure</title>", output_text)
@@ -3666,7 +4609,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_update_issue_edit_comment(self, p_send_email, p_ugt):
-        """ Test the issues edit comment endpoint """
+        """Test the issues edit comment endpoint"""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3910,7 +4853,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_git_urls(self, p_send_email, p_ugt):
-        """ Check that the url to the git repo for issues is present/absent when
+        """Check that the url to the git repo for issues is present/absent when
         it should.
         """
         p_send_email.return_value = True
@@ -3962,7 +4905,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_tags_issue_disabled(self):
-        """ Test the update_tags endpoint. """
+        """Test the update_tags endpoint."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
 
@@ -4031,7 +4974,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_update_tags(self, p_send_email, p_ugt):
-        """ Test the update_tags endpoint. """
+        """Test the update_tags endpoint."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -4381,7 +5324,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_update_tags_with_colon(self, p_send_email, p_ugt):
-        """ Test the update_tags endpoint with a tag containing a colon. """
+        """Test the update_tags endpoint with a tag containing a colon."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -4449,7 +5392,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_update_tags_with_coma(self, p_send_email, p_ugt):
-        """ Test the update_tags endpoint with a tag containing a coma. """
+        """Test the update_tags endpoint with a tag containing a coma."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -4511,8 +5454,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue_namespace_comment(self, p_send_email, p_ugt):
-        """ Test comment on the view_issue endpoint on namespaced project.
-        """
+        """Test comment on the view_issue endpoint on namespaced project."""
         # Create the project ns/test
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
@@ -4577,7 +5519,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue_comment_and_close(self, p_send_email, p_ugt):
-        """ Test if the comment and close button shows up on a ticket opened
+        """Test if the comment and close button shows up on a ticket opened
         by the user
         """
         # Create the project ns/test
@@ -4623,8 +5565,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue_forked_namespace_comment(self, p_send_email, p_ugt):
-        """ Test comment on the view_issue endpoint on namespaced project.
-        """
+        """Test comment on the view_issue endpoint on namespaced project."""
         # Create the project ns/test
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
@@ -4707,7 +5648,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue_closed(self, p_send_email, p_ugt):
-        """ Test viewing a closed issue. """
+        """Test viewing a closed issue."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -4817,7 +5758,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_add_reaction(self, p_send_email, p_ugt):
-        """ Test adding a reaction to an issue comment."""
+        """Test adding a reaction to an issue comment."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
diff --git a/tests/test_pagure_flask_ui_issues_acl_checks.py b/tests/test_pagure_flask_ui_issues_acl_checks.py
index f8bc550..4f890ab 100644
--- a/tests/test_pagure_flask_ui_issues_acl_checks.py
+++ b/tests/test_pagure_flask_ui_issues_acl_checks.py
@@ -37,12 +37,12 @@ import tests
 
 
 class PagureFlaskIssuesACLtests(tests.Modeltests):
-    """ Tests for flask issues controller of pagure for acls """
+    """Tests for flask issues controller of pagure for acls"""
 
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue_no_access(self, p_send_email, p_ugt):
-        """ Test the view_issue endpoint. when a user has no access on repo """
+        """Test the view_issue endpoint. when a user has no access on repo"""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -295,7 +295,7 @@ class PagureFlaskIssuesACLtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue_ticket_access(self, p_send_email, p_ugt):
-        """ Test the view_issue endpoint. when a user has ticket access on repo """
+        """Test the view_issue endpoint. when a user has ticket access on repo"""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -550,7 +550,7 @@ class PagureFlaskIssuesACLtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue_commit_access(self, p_send_email, p_ugt):
-        """ Test the view_issue endpoint. when a user has commit access on repo """
+        """Test the view_issue endpoint. when a user has commit access on repo"""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -804,7 +804,7 @@ class PagureFlaskIssuesACLtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_view_issue_admin_access(self, p_send_email, p_ugt):
-        """ Test the view_issue endpoint. when a user has admin access on repo """
+        """Test the view_issue endpoint. when a user has admin access on repo"""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
diff --git a/tests/test_pagure_flask_ui_issues_open_access.py b/tests/test_pagure_flask_ui_issues_open_access.py
index 7085d95..4fa6ff7 100644
--- a/tests/test_pagure_flask_ui_issues_open_access.py
+++ b/tests/test_pagure_flask_ui_issues_open_access.py
@@ -41,10 +41,10 @@ import tests
 
 
 class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
-    """ Tests for flask issues controller of pagure """
+    """Tests for flask issues controller of pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskIssuesOpenAccesstests, self).setUp()
 
         tests.create_projects(self.session)
@@ -64,8 +64,8 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_new_issue_with_metadata(self):
-        """ Test the new_issue endpoint when the user has access to the
-        project. """
+        """Test the new_issue endpoint when the user has access to the
+        project."""
 
         user = tests.FakeUser()
         user.username = "foo"
@@ -122,7 +122,7 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_new_issue_with_metadata_not_user(self):
-        """ Test the new_issue endpoint when the user does not have access
+        """Test the new_issue endpoint when the user does not have access
         to the project but still tries to.
         """
 
@@ -187,7 +187,7 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_view_issue(self):
-        """ Test the view_issue endpoint. """
+        """Test the view_issue endpoint."""
         output = self.app.get("/test/issue/1")
         self.assertEqual(output.status_code, 404)
 
@@ -286,7 +286,7 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_view_issue_user_ticket(self):
-        """ Test the view_issue endpoint. """
+        """Test the view_issue endpoint."""
 
         output = self.app.get("/test/issue/1")
         self.assertEqual(output.status_code, 404)
@@ -355,7 +355,7 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_view_issue_custom_field_user_ticket(self):
-        """ Test the view_issue endpoint. """
+        """Test the view_issue endpoint."""
         output = self.app.get("/test/issue/1")
         self.assertEqual(output.status_code, 404)
 
@@ -475,7 +475,7 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_view_issue_non_ascii_milestone(self):
-        """ Test the view_issue endpoint with non-ascii milestone. """
+        """Test the view_issue endpoint with non-ascii milestone."""
         output = self.app.get("/test/issue/1")
         self.assertEqual(output.status_code, 404)
 
@@ -516,8 +516,8 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_view_issue_list_no_data(self):
-        """ Test the view_issue endpoint when the issue has a custom field
-        of type list with no data attached. """
+        """Test the view_issue endpoint when the issue has a custom field
+        of type list with no data attached."""
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
 
         # Add custom fields to the project
@@ -561,7 +561,7 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_issue(self):
-        """ Test the update_issue endpoint. """
+        """Test the update_issue endpoint."""
         output = self.app.get("/test/issue/1/update")
         self.assertEqual(output.status_code, 302)
 
@@ -873,7 +873,7 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_issue_depend(self):
-        """ Test adding dependency via the update_issue endpoint. """
+        """Test adding dependency via the update_issue endpoint."""
         # Create issues to play with
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         msg = pagure.lib.query.new_issue(
@@ -957,7 +957,7 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_issue_block(self):
-        """ Test adding blocked issue via the update_issue endpoint. """
+        """Test adding blocked issue via the update_issue endpoint."""
         # Create issues to play with
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         msg = pagure.lib.query.new_issue(
@@ -1040,7 +1040,7 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_issue_edit_comment(self):
-        """ Test the issues edit comment endpoint """
+        """Test the issues edit comment endpoint"""
         # Create issues to play with
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         msg = pagure.lib.query.new_issue(
@@ -1276,7 +1276,7 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_view_issue_closed(self):
-        """ Test viewing a closed issue. """
+        """Test viewing a closed issue."""
         # Create issues to play with
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         msg = pagure.lib.query.new_issue(
diff --git a/tests/test_pagure_flask_ui_issues_private.py b/tests/test_pagure_flask_ui_issues_private.py
index d9566e3..a8660b1 100644
--- a/tests/test_pagure_flask_ui_issues_private.py
+++ b/tests/test_pagure_flask_ui_issues_private.py
@@ -25,12 +25,11 @@ import tests  # noqa
 
 
 class PagureFlaskIssuesPrivatetests(tests.Modeltests):
-    """ Tests for flask issues controller of pagure with private tickets
-    """
+    """Tests for flask issues controller of pagure with private tickets"""
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskIssuesPrivatetests, self).setUp()
 
         # Create a 3rd user
@@ -74,7 +73,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
         self.assertEqual(msg.title, "Test issue #2")
 
     def test_issue_list_anonymous(self):
-        """ Test the list of issues when user is logged out. """
+        """Test the list of issues when user is logged out."""
 
         output = self.app.get("/test/issues")
         self.assertEqual(output.status_code, 200)
@@ -86,8 +85,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
         )
 
     def test_issue_list_admin(self):
-        """ Test the list of issues when user is an admin of the project.
-        """
+        """Test the list of issues when user is an admin of the project."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -101,8 +99,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
             )
 
     def test_issue_list_author(self):
-        """ Test the list of issues when user is an admin of the project.
-        """
+        """Test the list of issues when user is an admin of the project."""
 
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
@@ -116,7 +113,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
             )
 
     def test_issue_list_authenticated(self):
-        """ Test the list of issues when user is authenticated but has no
+        """Test the list of issues when user is authenticated but has no
         special access to the project.
         """
 
@@ -132,7 +129,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
             )
 
     def test_issue_list_authenticated_ticket(self):
-        """ Test the list of issues when user is authenticated but has
+        """Test the list of issues when user is authenticated but has
         ticket level access to the project.
         """
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -158,7 +155,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
             )
 
     def test_issue_list_authenticated_commit(self):
-        """ Test the list of issues when user is authenticated but has
+        """Test the list of issues when user is authenticated but has
         commit level access to the project.
         """
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -184,7 +181,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
             )
 
     def test_issue_list_authenticated_assigned(self):
-        """ Test the list of issues when user is authenticated and is
+        """Test the list of issues when user is authenticated and is
         assigned to one of the issue.
         """
 
@@ -206,13 +203,13 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
             )
 
     def test_view_issue_anonymous(self):
-        """ Test accessing a private ticket when user is logged out. """
+        """Test accessing a private ticket when user is logged out."""
 
         output = self.app.get("/test/issue/1")
         self.assertEqual(output.status_code, 404)
 
     def test_view_issue_admin(self):
-        """ Test accessing a private ticket when user is an admin of the
+        """Test accessing a private ticket when user is an admin of the
         project.
         """
 
@@ -232,8 +229,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
             )
 
     def test_view_issue_author(self):
-        """ Test accessing a private ticket when user opened the ticket.
-        """
+        """Test accessing a private ticket when user opened the ticket."""
 
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
@@ -251,7 +247,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
             )
 
     def test_view_issue_authenticated(self):
-        """ Test accessing a private ticket when user is authenticated but
+        """Test accessing a private ticket when user is authenticated but
         has no special access to the project.
         """
 
@@ -261,7 +257,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
             self.assertEqual(output.status_code, 404)
 
     def test_view_issue_authenticated_ticket(self):
-        """ Test accessing a private ticket when user is authenticated and
+        """Test accessing a private ticket when user is authenticated and
         has ticket level access to the project.
         """
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -281,7 +277,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
             self.assertEqual(output.status_code, 404)
 
     def test_view_issue_authenticated_commit(self):
-        """ Test accessing a private ticket when user is authenticated and
+        """Test accessing a private ticket when user is authenticated and
         has commit level access to the project.
         """
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -311,7 +307,7 @@ class PagureFlaskIssuesPrivatetests(tests.Modeltests):
             )
 
     def test_view_issue_authenticated_assigned(self):
-        """ Test accessing a private ticket when user is authenticated and
+        """Test accessing a private ticket when user is authenticated and
         is assigned to one of the issue.
         """
 
diff --git a/tests/test_pagure_flask_ui_issues_read_only.py b/tests/test_pagure_flask_ui_issues_read_only.py
index ef57ccb..b0ad8f3 100644
--- a/tests/test_pagure_flask_ui_issues_read_only.py
+++ b/tests/test_pagure_flask_ui_issues_read_only.py
@@ -26,12 +26,11 @@ import tests  # noqa
 
 
 class PagureFlaskIssuesReadOnlytests(tests.Modeltests):
-    """ Tests for flask issues controller of pagure with read-only tickets
-    """
+    """Tests for flask issues controller of pagure with read-only tickets"""
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskIssuesReadOnlytests, self).setUp()
 
         tests.create_projects(self.session)
@@ -71,7 +70,7 @@ class PagureFlaskIssuesReadOnlytests(tests.Modeltests):
         self.assertEqual(msg.title, "Test issue #2")
 
     def test_issue_list_authenticated_commit(self):
-        """ Test the list of issues when user is authenticated and has
+        """Test the list of issues when user is authenticated and has
         access to the project.
         """
 
@@ -88,8 +87,7 @@ class PagureFlaskIssuesReadOnlytests(tests.Modeltests):
             )
 
     def test_field_comment(self):
-        """ Test if the field commit is present on the issue page.
-        """
+        """Test if the field commit is present on the issue page."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.get("/test/issue/1")
@@ -107,8 +105,7 @@ class PagureFlaskIssuesReadOnlytests(tests.Modeltests):
             self.assertIn("This issue tracker is read-only.", output_text)
 
     def test_update_ticket(self):
-        """ Test updating a ticket.
-        """
+        """Test updating a ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post(
@@ -125,8 +122,7 @@ class PagureFlaskIssuesReadOnlytests(tests.Modeltests):
             )
 
     def test_edit_comment(self):
-        """ Test editing a comment from a ticket.
-        """
+        """Test editing a comment from a ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post(
@@ -143,8 +139,7 @@ class PagureFlaskIssuesReadOnlytests(tests.Modeltests):
             )
 
     def test_edit_ticket(self):
-        """ Test editing a ticket.
-        """
+        """Test editing a ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post(
@@ -161,8 +156,7 @@ class PagureFlaskIssuesReadOnlytests(tests.Modeltests):
             )
 
     def test_new_issue(self):
-        """ Test creating a new ticket.
-        """
+        """Test creating a new ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/test/new_issue/", data={})
@@ -177,8 +171,7 @@ class PagureFlaskIssuesReadOnlytests(tests.Modeltests):
             )
 
     def test_deleting_issue(self):
-        """ Test deleting a new ticket.
-        """
+        """Test deleting a new ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/test/issue/1/drop", data={})
@@ -193,8 +186,7 @@ class PagureFlaskIssuesReadOnlytests(tests.Modeltests):
             )
 
     def test_uploading_to_issue(self):
-        """ Test uploading to a new ticket.
-        """
+        """Test uploading to a new ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/test/issue/1/upload", data={})
@@ -210,17 +202,15 @@ class PagureFlaskIssuesReadOnlytests(tests.Modeltests):
 
 
 class PagureFlaskAPIIssuesReadOnlytests(PagureFlaskIssuesReadOnlytests):
-    """ Tests for flask API issues controller of pagure with read-only tickets
-    """
+    """Tests for flask API issues controller of pagure with read-only tickets"""
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskAPIIssuesReadOnlytests, self).setUp()
 
     def test_api_new_issue(self):
-        """ Test creating a new ticket.
-        """
+        """Test creating a new ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/api/0/test/new_issue", data={})
@@ -235,7 +225,7 @@ class PagureFlaskAPIIssuesReadOnlytests(PagureFlaskIssuesReadOnlytests):
             )
 
     def test_api_change_status_issue(self):
-        """ Test closing a ticket. """
+        """Test closing a ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/api/0/test/issue/1/status", data={})
@@ -250,7 +240,7 @@ class PagureFlaskAPIIssuesReadOnlytests(PagureFlaskIssuesReadOnlytests):
             )
 
     def test_api_change_milestone_issue(self):
-        """ Test change the milestone of a ticket. """
+        """Test change the milestone of a ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/api/0/test/issue/1/milestone", data={})
@@ -265,7 +255,7 @@ class PagureFlaskAPIIssuesReadOnlytests(PagureFlaskIssuesReadOnlytests):
             )
 
     def test_api_comment_issue(self):
-        """ Test comment on a ticket. """
+        """Test comment on a ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/api/0/test/issue/1/comment", data={})
@@ -280,7 +270,7 @@ class PagureFlaskAPIIssuesReadOnlytests(PagureFlaskIssuesReadOnlytests):
             )
 
     def test_api_assign_issue(self):
-        """ Test assigning a ticket. """
+        """Test assigning a ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/api/0/test/issue/1/assign", data={})
@@ -295,7 +285,7 @@ class PagureFlaskAPIIssuesReadOnlytests(PagureFlaskIssuesReadOnlytests):
             )
 
     def test_api_subscribe_issue(self):
-        """ Test subscribing to a ticket. """
+        """Test subscribing to a ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/api/0/test/issue/1/subscribe", data={})
@@ -310,7 +300,7 @@ class PagureFlaskAPIIssuesReadOnlytests(PagureFlaskIssuesReadOnlytests):
             )
 
     def test_api_update_custom_field(self):
-        """ Test updating a specific custom fields on a ticket. """
+        """Test updating a specific custom fields on a ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/api/0/test/issue/1/custom/foo", data={})
@@ -325,7 +315,7 @@ class PagureFlaskAPIIssuesReadOnlytests(PagureFlaskIssuesReadOnlytests):
             )
 
     def test_api_update_custom_fields(self):
-        """ Test updating custom fields on a ticket. """
+        """Test updating custom fields on a ticket."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/api/0/test/issue/1/custom", data={})
@@ -341,13 +331,13 @@ class PagureFlaskAPIIssuesReadOnlytests(PagureFlaskIssuesReadOnlytests):
 
 
 class PagureFlaskIssuesAndPRDisabledtests(tests.Modeltests):
-    """ Tests for flask issues controller of pagure with tickets and PRs
+    """Tests for flask issues controller of pagure with tickets and PRs
     disabled.
     """
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskIssuesAndPRDisabledtests, self).setUp()
 
         tests.create_projects(self.session)
@@ -388,8 +378,7 @@ class PagureFlaskIssuesAndPRDisabledtests(tests.Modeltests):
         self.assertEqual(msg.title, "Test issue #2")
 
     def test_edit_tag(self):
-        """ Test editing a ticket tag.
-        """
+        """Test editing a ticket tag."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/test/tag/tag1/edit", data={})
@@ -404,8 +393,7 @@ class PagureFlaskIssuesAndPRDisabledtests(tests.Modeltests):
             )
 
     def test_drop_tags(self):
-        """ Test dropping a ticket tag.
-        """
+        """Test dropping a ticket tag."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/test/droptag/", data={})
diff --git a/tests/test_pagure_flask_ui_issues_templates.py b/tests/test_pagure_flask_ui_issues_templates.py
index ee0e2f4..ff8849c 100644
--- a/tests/test_pagure_flask_ui_issues_templates.py
+++ b/tests/test_pagure_flask_ui_issues_templates.py
@@ -27,8 +27,7 @@ import tests
 
 
 def create_templates(repopath):
-    """ Create a couple of templates at the specified repo.
-    """
+    """Create a couple of templates at the specified repo."""
     clone_repo = pygit2.Repository(repopath)
 
     # Create the RFE template
@@ -100,12 +99,12 @@ def create_templates(repopath):
 
 
 class PagureFlaskIssuesTemplatetests(tests.Modeltests):
-    """ Tests for flask issues controller of pagure """
+    """Tests for flask issues controller of pagure"""
 
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, run before every tests. """
+        """Set up the environnment, run before every tests."""
         super(PagureFlaskIssuesTemplatetests, self).setUp()
 
         pagure.config.config["TICKETS_FOLDER"] = os.path.join(
@@ -127,8 +126,7 @@ class PagureFlaskIssuesTemplatetests(tests.Modeltests):
         create_templates(repopath)
 
     def test_new_issue_no_template(self):
-        """ Test the new_issue endpoint when the project has no templates.
-        """
+        """Test the new_issue endpoint when the project has no templates."""
 
         user = tests.FakeUser()
         with tests.user_set(self.app.application, user):
@@ -142,7 +140,7 @@ class PagureFlaskIssuesTemplatetests(tests.Modeltests):
             self.assertNotIn("Issue Templates", output_text)
 
     def test_new_issue_w_template(self):
-        """ Test the new_issue endpoint when the project has templates. """
+        """Test the new_issue endpoint when the project has templates."""
 
         user = tests.FakeUser()
         with tests.user_set(self.app.application, user):
@@ -173,7 +171,7 @@ class PagureFlaskIssuesTemplatetests(tests.Modeltests):
             )
 
     def test_new_issue_w_specific_template(self):
-        """ Test the new_issue endpoint when the project has templates. """
+        """Test the new_issue endpoint when the project has templates."""
 
         user = tests.FakeUser()
         with tests.user_set(self.app.application, user):
@@ -204,7 +202,7 @@ class PagureFlaskIssuesTemplatetests(tests.Modeltests):
             )
 
     def test_get_ticket_template_no_csrf(self):
-        """ Test the get_ticket_template endpoint when the project has no
+        """Test the get_ticket_template endpoint when the project has no
         templates.
         """
 
@@ -218,7 +216,7 @@ class PagureFlaskIssuesTemplatetests(tests.Modeltests):
             )
 
     def test_get_ticket_template_no_template_specified(self):
-        """ Test the get_ticket_template endpoint when not specifying which
+        """Test the get_ticket_template endpoint when not specifying which
         template to get.
         """
 
@@ -234,7 +232,7 @@ class PagureFlaskIssuesTemplatetests(tests.Modeltests):
             )
 
     def test_get_ticket_template_no_project(self):
-        """ Test the get_ticket_template endpoint when the project does not
+        """Test the get_ticket_template endpoint when the project does not
         exist.
         """
 
@@ -246,7 +244,7 @@ class PagureFlaskIssuesTemplatetests(tests.Modeltests):
             self.assertEqual(output.status_code, 404)
 
     def test_get_ticket_template_no_template(self):
-        """ Test the get_ticket_template endpoint when the project has no
+        """Test the get_ticket_template endpoint when the project has no
         templates.
         """
 
@@ -264,7 +262,7 @@ class PagureFlaskIssuesTemplatetests(tests.Modeltests):
             )
 
     def test_get_ticket_template_issue_tracker_disabled(self):
-        """ Test the get_ticket_template endpoint when the project has
+        """Test the get_ticket_template endpoint when the project has
         disabled its issue tracker.
         """
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -292,7 +290,7 @@ class PagureFlaskIssuesTemplatetests(tests.Modeltests):
             )
 
     def test_get_ticket_template_w_template(self):
-        """ Test the get_ticket_template endpoint when the project has
+        """Test the get_ticket_template endpoint when the project has
         templates.
         """
 
@@ -311,7 +309,7 @@ class PagureFlaskIssuesTemplatetests(tests.Modeltests):
             )
 
     def test_get_ticket_template_w_template_namespace(self):
-        """ Test the get_ticket_template endpoint when the project has
+        """Test the get_ticket_template endpoint when the project has
         templates and a namespace.
         """
 
diff --git a/tests/test_pagure_flask_ui_login.py b/tests/test_pagure_flask_ui_login.py
index f11a2b2..5927cb0 100644
--- a/tests/test_pagure_flask_ui_login.py
+++ b/tests/test_pagure_flask_ui_login.py
@@ -37,10 +37,10 @@ import pagure.ui.login
 
 
 class PagureFlaskLogintests(tests.SimplePagureTest):
-    """ Tests for flask app controller of pagure """
+    """Tests for flask app controller of pagure"""
 
     def setUp(self):
-        """ Create the application with PAGURE_AUTH being local. """
+        """Create the application with PAGURE_AUTH being local."""
         super(PagureFlaskLogintests, self).setUp()
 
         app = pagure.flask_app.create_app(
@@ -53,7 +53,7 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
 
     @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
     def test_front_page(self):
-        """ Test the front page. """
+        """Test the front page."""
         # First access the front page
         output = self.app.get("/")
         self.assertEqual(output.status_code, 200)
@@ -64,7 +64,7 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
     @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_new_user(self):
-        """ Test the new_user endpoint. """
+        """Test the new_user endpoint."""
 
         # Check before:
         items = pagure.lib.query.search_user(self.session)
@@ -149,10 +149,34 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
         items = pagure.lib.query.search_user(self.session)
         self.assertEqual(3, len(items))
 
+    @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
+    @patch.dict("pagure.config.config", {"ALLOW_USER_REGISTRATION": False})
+    @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
+    def test_new_user_disabled(self):
+        """Test the disabling of the new_user endpoint."""
+
+        # Check before:
+        items = pagure.lib.query.search_user(self.session)
+        self.assertEqual(2, len(items))
+
+        # Attempt to access the new user page
+        output = self.app.get("/user/new", follow_redirects=True)
+        self.assertEqual(output.status_code, 200)
+        self.assertIn(
+            "<title>Login - Pagure</title>", output.get_data(as_text=True)
+        )
+        self.assertIn(
+            "User registration is disabled.", output.get_data(as_text=True)
+        )
+
+        # Check after:
+        items = pagure.lib.query.search_user(self.session)
+        self.assertEqual(2, len(items))
+
     @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
     @patch.dict("pagure.config.config", {"CHECK_SESSION_IP": False})
     def test_do_login(self):
-        """ Test the do_login endpoint. """
+        """Test the do_login endpoint."""
 
         output = self.app.get("/login/")
         self.assertEqual(output.status_code, 200)
@@ -338,7 +362,8 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
             output.get_data(as_text=True),
         )
         self.assertIn(
-            "Username or password invalid.", output.get_data(as_text=True),
+            "Username or password invalid.",
+            output.get_data(as_text=True),
         )
 
         # Check the password is still not of a known version
@@ -394,7 +419,7 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
     @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
     @patch.dict("pagure.config.config", {"CHECK_SESSION_IP": False})
     def test_do_login_and_redirect(self):
-        """ Test the do_login endpoint with a non-default redirect. """
+        """Test the do_login endpoint with a non-default redirect."""
         # This has all the data needed
         data = {
             "username": "foouser",
@@ -449,11 +474,15 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
             '<span class="d-none d-md-inline">Settings</span>', output_text
         )
 
+        output = self.app.get("/login/?next=%2f%2f%09%2fgoogle.fr")
+        self.assertEqual(output.status_code, 302)
+        self.assertEqual(output.location, "http://localhost/google.fr")
+
     @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
     @patch.dict("pagure.config.config", {"CHECK_SESSION_IP": False})
     def test_has_settings(self):
-        """ Test that user can see the Settings button when they are logged
-        in. """
+        """Test that user can see the Settings button when they are logged
+        in."""
         # Create a local user
         self.test_new_user()
         self.session.commit()
@@ -493,8 +522,8 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
     @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_non_ascii_password(self):
-        """ Test login and create user functionality when the password is
-            non-ascii.
+        """Test login and create user functionality when the password is
+        non-ascii.
         """
 
         # Check before:
@@ -672,7 +701,7 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
         self.assertEqual(item.token, None)
 
     def test_confirm_user(self):
-        """ Test the confirm_user endpoint. """
+        """Test the confirm_user endpoint."""
 
         output = self.app.get("/confirm/foo", follow_redirects=True)
         self.assertEqual(output.status_code, 200)
@@ -708,7 +737,7 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
     @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_lost_password(self):
-        """ Test the lost_password endpoint. """
+        """Test the lost_password endpoint."""
 
         output = self.app.get("/password/lost")
         self.assertEqual(output.status_code, 200)
@@ -785,7 +814,7 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
     @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_reset_password(self):
-        """ Test the reset_password endpoint. """
+        """Test the reset_password endpoint."""
 
         output = self.app.get("/password/reset/foo", follow_redirects=True)
         self.assertEqual(output.status_code, 200)
@@ -862,7 +891,7 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
     )
     @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
     def test_change_password(self):
-        """ Test the change_password endpoint. """
+        """Test the change_password endpoint."""
 
         # Not logged in, redirects
         output = self.app.get("/password/change", follow_redirects=True)
@@ -1009,7 +1038,7 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
 
     @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
     def test_logout(self):
-        """ Test the auth_logout endpoint for local login. """
+        """Test the auth_logout endpoint for local login."""
 
         output = self.app.get("/logout/", follow_redirects=True)
         self.assertEqual(output.status_code, 200)
@@ -1043,9 +1072,17 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
                 output.get_data(as_text=True),
             )
 
+        user = tests.FakeUser(username="foo")
+        with tests.user_set(self.app.application, user):
+            output = self.app.get("/logout/?next=%2f%2f%09%2fgoogle.fr")
+            self.assertEqual(output.status_code, 302)
+            self.assertTrue(
+                output.headers["location"] in ("http://localhost/google.fr",)
+            )
+
     @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
     def test_settings_admin_session_timedout(self):
-        """ Test the admin_session_timedout with settings endpoint. """
+        """Test the admin_session_timedout with settings endpoint."""
         lifetime = pagure.config.config.get(
             "ADMIN_SESSION_LIFETIME", datetime.timedelta(minutes=15)
         )
@@ -1060,7 +1097,14 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
             # redirect again for the login page
             output = self.app.get("/settings/")
             self.assertEqual(output.status_code, 302)
-            self.assertIn("http://localhost/login/", output.location)
+            self.assertTrue(
+                output.location
+                in (
+                    "http://localhost/login/",
+                    "/login/?next=http%3A%2F%2Flocalhost%2Fsettings%2F",
+                    "http://localhost/login/?next=http%3A%2F%2Flocalhost%2Fsettings%2F",
+                )
+            )
         # session did not expire
         user.login_time = datetime.datetime.utcnow() - lifetime + td1
         with tests.user_set(self.app.application, user):
@@ -1069,7 +1113,7 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
 
     @patch("flask.flash")
     def test_admin_session_timedout(self, flash):
-        """ Test the call to admin_session_timedout. """
+        """Test the call to admin_session_timedout."""
         lifetime = pagure.config.config.get(
             "ADMIN_SESSION_LIFETIME", datetime.timedelta(minutes=15)
         )
@@ -1091,7 +1135,7 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
 
     @patch.dict("pagure.config.config", {"PAGURE_AUTH": "local"})
     def test_force_logout(self):
-        """ Test forcing logout. """
+        """Test forcing logout."""
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user, keep_get_user=True):
             # Test that accessing settings works
@@ -1102,14 +1146,17 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
             data = {"csrf_token": self.get_csrf()}
             output = self.app.post("/settings/forcelogout/", data=data)
             self.assertEqual(output.status_code, 302)
-            self.assertEqual(
-                output.headers["Location"], "http://localhost/settings"
+            self.assertTrue(
+                output.headers["Location"]
+                in ("http://localhost/settings", "/settings")
             )
 
             # We should now get redirected to index, because our session became
             # invalid
             output = self.app.get("/settings")
-            self.assertEqual(output.headers["Location"], "http://localhost/")
+            self.assertTrue(
+                output.headers["Location"] in ("http://localhost/", "/")
+            )
 
             # After changing the login_time to now, the session should again be
             # valid
diff --git a/tests/test_pagure_flask_ui_no_master_branch.py b/tests/test_pagure_flask_ui_no_master_branch.py
index c3bef65..3ba928d 100644
--- a/tests/test_pagure_flask_ui_no_master_branch.py
+++ b/tests/test_pagure_flask_ui_no_master_branch.py
@@ -30,11 +30,10 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureFlaskNoMasterBranchtests(tests.SimplePagureTest):
-    """ Tests for flask application when the git repo has no master branch.
-    """
+    """Tests for flask application when the git repo has no master branch."""
 
     def set_up_git_repo(self):
-        """ Set up the git repo to play with. """
+        """Set up the git repo to play with."""
 
         # Create a git repo to play with
         gitrepo = os.path.join(self.path, "repos", "test.git")
@@ -96,7 +95,7 @@ class PagureFlaskNoMasterBranchtests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_view_repo(self, send_email):
-        """ Test the view_repo endpoint when the git repo has no master
+        """Test the view_repo endpoint when the git repo has no master
         branch.
         """
         send_email.return_value = True
@@ -147,7 +146,7 @@ class PagureFlaskNoMasterBranchtests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_view_commits(self, send_email):
-        """ Test the view_commits endpoint when the git repo has no
+        """Test the view_commits endpoint when the git repo has no
         master branch.
         """
         send_email.return_value = True
@@ -184,7 +183,7 @@ class PagureFlaskNoMasterBranchtests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_view_file(self, send_email):
-        """ Test the view_file endpoint when the git repo has no
+        """Test the view_file endpoint when the git repo has no
         master branch.
         """
         send_email.return_value = True
@@ -227,7 +226,7 @@ class PagureFlaskNoMasterBranchtests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_view_raw_file(self, send_email):
-        """ Test the view_raw_file endpoint when the git repo has no
+        """Test the view_raw_file endpoint when the git repo has no
         master branch.
         """
         send_email.return_value = True
@@ -259,7 +258,7 @@ class PagureFlaskNoMasterBranchtests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_view_tree(self, send_email):
-        """ Test the view_tree endpoint when the git repo has no
+        """Test the view_tree endpoint when the git repo has no
         master branch.
         """
         send_email.return_value = True
@@ -303,7 +302,7 @@ class PagureFlaskNoMasterBranchtests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull(self, send_email):
-        """ Test the new_request_pull endpoint when the git repo has no
+        """Test the new_request_pull endpoint when the git repo has no
         master branch.
         """
         send_email.return_value = True
diff --git a/tests/test_pagure_flask_ui_oidc_login.py b/tests/test_pagure_flask_ui_oidc_login.py
new file mode 100644
index 0000000..4a33e13
--- /dev/null
+++ b/tests/test_pagure_flask_ui_oidc_login.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+
+"""
+ (c) 2020 - Copyright Red Hat Inc
+
+ Authors:
+   Aurelien Bompard <abompard@fedoraproject.org>
+
+"""
+
+from __future__ import unicode_literals
+
+__requires__ = ["SQLAlchemy >= 0.8"]
+import pkg_resources
+
+import unittest
+import json
+import sys
+import os
+
+import flask
+from mock import patch, Mock
+
+sys.path.insert(
+    0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
+)
+
+import pagure.lib
+import tests
+
+from pagure.ui.oidc_login import fas_user_from_oidc, oidc
+
+
+CLIENT_SECRETS = {
+    "web": {
+        "client_id": "dummy",
+        "client_secret": "dummy",
+        "auth_uri": "dummy-uri://",
+        "token_uri": "dummy-uri://",
+        "userinfo_uri": "dummy-uri://",
+        "redirect_uris": ["http://localhost:5000/oidc"],
+    }
+}
+
+
+class PagureFlaskOIDCLogintests(tests.SimplePagureTest):
+    """Tests for OIDC login in the flask app controller of pagure"""
+
+    populate_db = False
+
+    def setUp(self):
+        """Create the application with PAGURE_AUTH being local."""
+        super(PagureFlaskOIDCLogintests, self).setUp()
+
+        self.app = pagure.flask_app.create_app(
+            {"DB_URL": self.dbpath, "PAGURE_AUTH": "local"}
+        )
+        # Remove the log handlers for the tests
+        self.app.logger.handlers = []
+
+        secrets_path = os.path.join(self.path, "client_secrets.json")
+        self.config_patcher = patch.dict(
+            "pagure.config.config",
+            {
+                "OIDC_PAGURE_EMAIL": "email",
+                "OIDC_PAGURE_FULLNAME": "name",
+                "OIDC_PAGURE_USERNAME": "preferred_username",
+                "OIDC_PAGURE_SSH_KEY": "ssh_key",
+                "OIDC_PAGURE_GROUPS": "groups",
+                "OIDC_CLIENT_SECRETS": secrets_path,
+            },
+        )
+        self.config_patcher.start()
+
+        with open(secrets_path, "w") as secrets:
+            secrets.write(json.dumps(CLIENT_SECRETS))
+
+        oidc.init_app(self.app)
+
+        self.request_context = self.app.test_request_context("/")
+        self.request_context.push()
+        flask.session["oidc_logintime"] = "dummy-logintime"
+        flask.g.session = Mock()  # the DB session should be here
+        flask.g.oidc_id_token = {"sub": "dummy"}
+        self.user_info = {
+            "email": "dummy@example.com",
+            "name": "Dummy User",
+            "preferred_username": "dummy",
+        }
+
+    def tearDown(self):
+        self.request_context.pop()
+        self.config_patcher.stop()
+
+    def test_fas_user_from_oidc(self):
+        """Test the user creation function."""
+        user_info = self.user_info.copy()
+        flask.g._oidc_userinfo = user_info
+        fas_user_from_oidc()
+        self.assertIsNotNone(getattr(flask.g, "fas_user", None))
+        self.assertEqual(flask.g.fas_user.username, "dummy")
+        self.assertEqual(flask.g.fas_user.fullname, "Dummy User")
+        self.assertIsNone(flask.g.fas_user.ssh_key)
+        self.assertEqual(flask.g.fas_user.groups, [])
+
+    def test_fas_user_from_oidc_groups(self):
+        """Test the user creation function."""
+        user_info = self.user_info.copy()
+        user_info["groups"] = ["group1", "group2"]
+        flask.g._oidc_userinfo = user_info
+        fas_user_from_oidc()
+        self.assertEqual(flask.g.fas_user.groups, ["group1", "group2"])
+
+    def test_fas_user_from_oidc_ssh(self):
+        """Test the user creation function."""
+        user_info = self.user_info.copy()
+        user_info["ssh_key"] = "dummy ssh key"
+        flask.g._oidc_userinfo = user_info
+        fas_user_from_oidc()
+        self.assertEqual(flask.g.fas_user.ssh_key, "dummy ssh key")
+
+    def test_fas_user_from_oidc_ssh_b64(self):
+        """The SSH key may be base64-encoded"""
+        user_info = self.user_info.copy()
+        user_info["ssh_key"] = "ZHVtbXkgc3NoIGtleQ=="
+        flask.g._oidc_userinfo = user_info
+        fas_user_from_oidc()
+        self.assertEqual(flask.g.fas_user.ssh_key, "dummy ssh key")
+
+
+if __name__ == "__main__":
+    unittest.main(verbosity=2)
diff --git a/tests/test_pagure_flask_ui_old_commit.py b/tests/test_pagure_flask_ui_old_commit.py
index 69f2e0d..a8e8232 100644
--- a/tests/test_pagure_flask_ui_old_commit.py
+++ b/tests/test_pagure_flask_ui_old_commit.py
@@ -26,10 +26,10 @@ import tests
 
 
 class PagureFlaskRepoOldUrltests(tests.SimplePagureTest):
-    """ Tests for flask app controller of pagure """
+    """Tests for flask app controller of pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskRepoOldUrltests, self).setUp()
 
         pagure.config.config["EMAIL_SEND"] = False
@@ -39,7 +39,7 @@ class PagureFlaskRepoOldUrltests(tests.SimplePagureTest):
 
     @patch.dict("pagure.config.config", {"OLD_VIEW_COMMIT_ENABLED": True})
     def test_view_commit_old_with_bogus_url(self):
-        """ Test the view_commit_old endpoint. """
+        """Test the view_commit_old endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -54,7 +54,7 @@ class PagureFlaskRepoOldUrltests(tests.SimplePagureTest):
 
     @patch.dict("pagure.config.config", {"OLD_VIEW_COMMIT_ENABLED": True})
     def test_view_commit_old(self):
-        """ Test the view_commit_old endpoint. """
+        """Test the view_commit_old endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -83,7 +83,7 @@ class PagureFlaskRepoOldUrltests(tests.SimplePagureTest):
         )
 
         self.assertTrue(
-            'title="View file as of %s"' % commit.oid.hex[0:6]
+            'title="View file as of %s"' % commit.oid.hex[0:7]
             in output.get_data(as_text=True)
         )
 
@@ -168,7 +168,7 @@ class PagureFlaskRepoOldUrltests(tests.SimplePagureTest):
         )
 
         self.assertTrue(
-            'title="View file as of %s"' % commit.oid.hex[0:6]
+            'title="View file as of %s"' % commit.oid.hex[0:7]
             in output.get_data(as_text=True)
         )
 
diff --git a/tests/test_pagure_flask_ui_plugins.py b/tests/test_pagure_flask_ui_plugins.py
index 4e21f77..3d7c364 100644
--- a/tests/test_pagure_flask_ui_plugins.py
+++ b/tests/test_pagure_flask_ui_plugins.py
@@ -25,17 +25,17 @@ import tests
 
 
 class FakeForm(wtforms.Form):
-    """ Form to configure the mail hook. """
+    """Form to configure the mail hook."""
 
     field1 = wtforms.StringField("Title", [pagure.hooks.RequiredIf("active")])
     field2 = wtforms.BooleanField("Title2", [wtforms.validators.Optional()])
 
 
 class PagureFlaskPluginstests(tests.SimplePagureTest):
-    """ Tests for flask plugins controller of pagure """
+    """Tests for flask plugins controller of pagure"""
 
     def test_get_plugin_names(self):
-        """ Test the get_plugin_names function. """
+        """Test the get_plugin_names function."""
         names = pagure.lib.plugins.get_plugin_names()
         self.assertEqual(
             sorted(names),
@@ -56,12 +56,12 @@ class PagureFlaskPluginstests(tests.SimplePagureTest):
         )
 
     def test_get_plugin(self):
-        """ Test the get_plugin function. """
+        """Test the get_plugin function."""
         name = pagure.lib.plugins.get_plugin("Mail")
         self.assertEqual(str(name), "<class 'pagure.hooks.mail.Mail'>")
 
     def test_view_plugin_page(self):
-        """ Test the view_plugin_page endpoint. """
+        """Test the view_plugin_page endpoint."""
 
         # No Git repo
         output = self.app.get("/foo/settings/Mail")
@@ -130,7 +130,7 @@ class PagureFlaskPluginstests(tests.SimplePagureTest):
             self.assertIn("Hook Mail deactivated", output_text)
 
     def test_RequiredIf(self):
-        """ Test the behavior of the RequiredIf validator. """
+        """Test the behavior of the RequiredIf validator."""
         form = FakeForm()
 
         try:
diff --git a/tests/test_pagure_flask_ui_plugins_default_hook.py b/tests/test_pagure_flask_ui_plugins_default_hook.py
index e0239f7..e754255 100644
--- a/tests/test_pagure_flask_ui_plugins_default_hook.py
+++ b/tests/test_pagure_flask_ui_plugins_default_hook.py
@@ -31,10 +31,10 @@ import tests
 
 
 class PagureFlaskPluginDefaultHooktests(tests.Modeltests):
-    """ Tests for default_hook plugin of pagure """
+    """Tests for default_hook plugin of pagure"""
 
     def test_plugin_default_active_on_project(self):
-        """ Test that the default hook is active on random project. """
+        """Test that the default hook is active on random project."""
 
         tests.create_projects(self.session)
         test = pagure.lib.query.search_projects(self.session)[0]
diff --git a/tests/test_pagure_flask_ui_plugins_fedmsg.py b/tests/test_pagure_flask_ui_plugins_fedmsg.py
index 3289eec..9f2f1f6 100644
--- a/tests/test_pagure_flask_ui_plugins_fedmsg.py
+++ b/tests/test_pagure_flask_ui_plugins_fedmsg.py
@@ -25,10 +25,10 @@ import pagure.config
 
 
 class PagureFlaskPluginFedmsgtests(tests.SimplePagureTest):
-    """ Tests for fedmsg plugin of pagure """
+    """Tests for fedmsg plugin of pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskPluginFedmsgtests, self).setUp()
 
         tests.create_projects(self.session)
@@ -36,12 +36,12 @@ class PagureFlaskPluginFedmsgtests(tests.SimplePagureTest):
         tests.create_projects_git(os.path.join(self.path, "docs"))
 
     def tearDown(self):
-        """ Tear Down the environment after the tests. """
+        """Tear Down the environment after the tests."""
         super(PagureFlaskPluginFedmsgtests, self).tearDown()
         pagure.config.config["DOCS_FOLDER"] = None
 
     def test_plugin_fedmsg_defaul_page(self):
-        """ Test the fedmsg plugin endpoint's default page. """
+        """Test the fedmsg plugin endpoint's default page."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -93,7 +93,7 @@ class PagureFlaskPluginFedmsgtests(tests.SimplePagureTest):
             )
 
     def test_plugin_fedmsg_no_data(self):
-        """ Test the setting up the fedmsg plugin when there are no Docs
+        """Test the setting up the fedmsg plugin when there are no Docs
         folder.
         """
 
@@ -147,7 +147,7 @@ class PagureFlaskPluginFedmsgtests(tests.SimplePagureTest):
             )
 
     def test_plugin_fedmsg_activate(self):
-        """ Test the setting up the fedmsg plugin when there are no Docs
+        """Test the setting up the fedmsg plugin when there are no Docs
         folder.
         """
         pagure.config.config["DOCS_FOLDER"] = os.path.join(
@@ -210,7 +210,7 @@ class PagureFlaskPluginFedmsgtests(tests.SimplePagureTest):
             )
 
     def test_plugin_fedmsg_deactivate(self):
-        """ Test the setting up the fedmsg plugin when there are no Docs
+        """Test the setting up the fedmsg plugin when there are no Docs
         folder.
         """
         self.test_plugin_fedmsg_activate()
@@ -271,7 +271,7 @@ class PagureFlaskPluginFedmsgtests(tests.SimplePagureTest):
 
     @patch.dict("pagure.config.config", {"DOCS_FOLDER": None})
     def test_plugin_fedmsg_no_docs(self):
-        """ Test the setting up the fedmsg plugin when there are no Docs
+        """Test the setting up the fedmsg plugin when there are no Docs
         folder.
         """
 
diff --git a/tests/test_pagure_flask_ui_plugins_irc.py b/tests/test_pagure_flask_ui_plugins_irc.py
index 2dd2189..326bc22 100644
--- a/tests/test_pagure_flask_ui_plugins_irc.py
+++ b/tests/test_pagure_flask_ui_plugins_irc.py
@@ -22,10 +22,10 @@ import tests
 
 
 class PagureFlaskPluginIRCtests(tests.SimplePagureTest):
-    """ Tests for pagure_hook plugin of pagure """
+    """Tests for pagure_hook plugin of pagure"""
 
     def test_plugin_mail(self):
-        """ Test the irc plugin on/off endpoint. """
+        """Test the irc plugin on/off endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
diff --git a/tests/test_pagure_flask_ui_plugins_mail.py b/tests/test_pagure_flask_ui_plugins_mail.py
index 3258d1f..4dfdc40 100644
--- a/tests/test_pagure_flask_ui_plugins_mail.py
+++ b/tests/test_pagure_flask_ui_plugins_mail.py
@@ -23,10 +23,10 @@ import tests
 
 
 class PagureFlaskPluginMailtests(tests.SimplePagureTest):
-    """ Tests for flask plugins controller of pagure """
+    """Tests for flask plugins controller of pagure"""
 
     def test_plugin_mail(self):
-        """ Test the mail plugin on/off endpoint. """
+        """Test the mail plugin on/off endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
diff --git a/tests/test_pagure_flask_ui_plugins_mirror.py b/tests/test_pagure_flask_ui_plugins_mirror.py
index 87004d5..d2ffbb4 100644
--- a/tests/test_pagure_flask_ui_plugins_mirror.py
+++ b/tests/test_pagure_flask_ui_plugins_mirror.py
@@ -25,17 +25,17 @@ import tests
 
 
 class PagureFlaskPluginMirrortests(tests.Modeltests):
-    """ Tests for mirror plugin of pagure """
+    """Tests for mirror plugin of pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskPluginMirrortests, self).setUp()
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
 
     def test_valid_ssh_url_pattern(self):
-        """ Check a number of valide ssh target that the pattern should let
+        """Check a number of valide ssh target that the pattern should let
         through.
         """
         entries = [
@@ -50,7 +50,7 @@ class PagureFlaskPluginMirrortests(tests.Modeltests):
             self.assertIsNotNone(pagure.utils.ssh_urlpattern.match(el))
 
     def test_plugin_mirror_no_csrf(self):
-        """ Test setting up the mirror plugin with no csrf. """
+        """Test setting up the mirror plugin with no csrf."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -95,7 +95,7 @@ class PagureFlaskPluginMirrortests(tests.Modeltests):
             )
 
     def test_plugin_mirror_no_data(self):
-        """ Test the setting up the mirror plugin when there are no data
+        """Test the setting up the mirror plugin when there are no data
         provided in the request.
         """
 
@@ -141,7 +141,7 @@ class PagureFlaskPluginMirrortests(tests.Modeltests):
             )
 
     def test_plugin_mirror_invalid_target(self):
-        """ Test the setting up the mirror plugin when there are the target
+        """Test the setting up the mirror plugin when there are the target
         provided is invalid.
         """
 
@@ -210,8 +210,7 @@ class PagureFlaskPluginMirrortests(tests.Modeltests):
             )
 
     def test_setting_up_mirror(self):
-        """ Test the setting up the mirror plugin.
-        """
+        """Test the setting up the mirror plugin."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -255,8 +254,7 @@ class PagureFlaskPluginMirrortests(tests.Modeltests):
             )
 
     def test_plugin_mirror_deactivate(self):
-        """ Test the deactivating the mirror plugin.
-        """
+        """Test the deactivating the mirror plugin."""
         self.test_setting_up_mirror()
 
         user = tests.FakeUser(username="pingou")
diff --git a/tests/test_pagure_flask_ui_plugins_noff.py b/tests/test_pagure_flask_ui_plugins_noff.py
index 4cbf555..18277d4 100644
--- a/tests/test_pagure_flask_ui_plugins_noff.py
+++ b/tests/test_pagure_flask_ui_plugins_noff.py
@@ -23,10 +23,10 @@ import tests
 
 
 class PagureFlaskPluginNoFFtests(tests.SimplePagureTest):
-    """ Tests for Block non fast-forward pushes plugin of pagure """
+    """Tests for Block non fast-forward pushes plugin of pagure"""
 
     def test_plugin_noff(self):
-        """ Test the noff plugin on/off endpoint. """
+        """Test the noff plugin on/off endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
diff --git a/tests/test_pagure_flask_ui_plugins_pagure_ci.py b/tests/test_pagure_flask_ui_plugins_pagure_ci.py
index 08304c0..063bf9b 100644
--- a/tests/test_pagure_flask_ui_plugins_pagure_ci.py
+++ b/tests/test_pagure_flask_ui_plugins_pagure_ci.py
@@ -17,10 +17,10 @@ import tests
 
 
 class PagureFlaskPluginPagureCItests(tests.SimplePagureTest):
-    """ Tests for flask plugins controller of pagure """
+    """Tests for flask plugins controller of pagure"""
 
     def test_plugin_pagure_ci(self):
-        """ Test the pagure ci plugin on/off endpoint. """
+        """Test the pagure ci plugin on/off endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
@@ -260,7 +260,7 @@ class PagureFlaskPluginPagureCItests(tests.SimplePagureTest):
             )
 
     def test_plugin_pagure_ci_namespaced(self):
-        """ Test the pagure ci plugin on/off endpoint. """
+        """Test the pagure ci plugin on/off endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
@@ -356,7 +356,7 @@ class PagureFlaskPluginPagureCItests(tests.SimplePagureTest):
 
     @mock.patch("pagure.lib.tasks_services.trigger_jenkins_build")
     def test_plugin_pagure_ci_namespaced_auth(self, trigger_jenk):
-        """ Test the pagure ci plugin on/off endpoint. """
+        """Test the pagure ci plugin on/off endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
diff --git a/tests/test_pagure_flask_ui_plugins_pagure_hook.py b/tests/test_pagure_flask_ui_plugins_pagure_hook.py
index 2fad753..e4ac0df 100644
--- a/tests/test_pagure_flask_ui_plugins_pagure_hook.py
+++ b/tests/test_pagure_flask_ui_plugins_pagure_hook.py
@@ -25,10 +25,10 @@ import pagure.config
 
 
 class PagureFlaskPluginPagureHooktests(tests.SimplePagureTest):
-    """ Tests for pagure_hook plugin of pagure """
+    """Tests for pagure_hook plugin of pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskPluginPagureHooktests, self).setUp()
 
         tests.create_projects(self.session)
@@ -36,12 +36,12 @@ class PagureFlaskPluginPagureHooktests(tests.SimplePagureTest):
         tests.create_projects_git(os.path.join(self.path, "repos", "docs"))
 
     def tearDown(self):
-        """ Tear Down the environment after the tests. """
+        """Tear Down the environment after the tests."""
         super(PagureFlaskPluginPagureHooktests, self).tearDown()
         pagure.config.config["DOCS_FOLDER"] = None
 
     def test_plugin_mail_page(self):
-        """ Test the default page of the pagure hook plugin. """
+        """Test the default page of the pagure hook plugin."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -58,7 +58,7 @@ class PagureFlaskPluginPagureHooktests(tests.SimplePagureTest):
             )
 
     def test_plugin_mail_no_data(self):
-        """ Test the pagure hook plugin endpoint when no data is sent. """
+        """Test the pagure hook plugin endpoint when no data is sent."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -93,7 +93,7 @@ class PagureFlaskPluginPagureHooktests(tests.SimplePagureTest):
             )
 
     def test_plugin_mail_no_data_csrf(self):
-        """ Test the pagure hook plugin endpoint when no data is sent but
+        """Test the pagure hook plugin endpoint when no data is sent but
         the csrf token.
         """
 
@@ -160,8 +160,7 @@ class PagureFlaskPluginPagureHooktests(tests.SimplePagureTest):
             )
 
     def test_plugin_mail_activate_hook(self):
-        """ Test the pagure hook plugin endpoint when activating the hook.
-        """
+        """Test the pagure hook plugin endpoint when activating the hook."""
         pagure.config.config["DOCS_FOLDER"] = os.path.join(
             self.path, "repos", "docs"
         )
@@ -218,8 +217,7 @@ class PagureFlaskPluginPagureHooktests(tests.SimplePagureTest):
             )
 
     def test_plugin_mail_deactivate_hook(self):
-        """ Test the pagure hook plugin endpoint when activating the hook.
-        """
+        """Test the pagure hook plugin endpoint when activating the hook."""
         self.test_plugin_mail_activate_hook()
 
         user = tests.FakeUser(username="pingou")
@@ -285,7 +283,7 @@ class PagureFlaskPluginPagureHooktests(tests.SimplePagureTest):
 
     @patch.dict("pagure.config.config", {"DOCS_FOLDER": None})
     def test_plugin_mail_activate_hook_no_doc(self):
-        """ Test the pagure hook plugin endpoint when activating the hook
+        """Test the pagure hook plugin endpoint when activating the hook
         on a pagure instance that de-activated the doc repos.
         """
 
@@ -319,7 +317,7 @@ class PagureFlaskPluginPagureHooktests(tests.SimplePagureTest):
 
     @patch.dict("pagure.config.config", {"DOCS_FOLDER": None})
     def test_plugin_mail_deactivate_hook_no_doc(self):
-        """ Test the pagure hook plugin endpoint when activating then
+        """Test the pagure hook plugin endpoint when activating then
         deactivating the hook on a pagure instance that de-activated the
         doc repos.
         """
diff --git a/tests/test_pagure_flask_ui_plugins_pagure_no_new_branch.py b/tests/test_pagure_flask_ui_plugins_pagure_no_new_branch.py
index 4375f86..3901cde 100644
--- a/tests/test_pagure_flask_ui_plugins_pagure_no_new_branch.py
+++ b/tests/test_pagure_flask_ui_plugins_pagure_no_new_branch.py
@@ -25,10 +25,10 @@ import pagure.config
 
 
 class PagureFlaskPluginPagureNoNewBranchHooktests(tests.SimplePagureTest):
-    """ Tests for pagure_no_new_branches plugin of pagure """
+    """Tests for pagure_no_new_branches plugin of pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskPluginPagureNoNewBranchHooktests, self).setUp()
 
         tests.create_projects(self.session)
@@ -40,7 +40,7 @@ class PagureFlaskPluginPagureNoNewBranchHooktests(tests.SimplePagureTest):
             self.csrf_token = self.get_csrf()
 
     def test_plugin_pagure_ticket_no_data(self):
-        """ Test the pagure_ticket plugin on/off endpoint. """
+        """Test the pagure_ticket plugin on/off endpoint."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -80,7 +80,7 @@ class PagureFlaskPluginPagureNoNewBranchHooktests(tests.SimplePagureTest):
             )
 
     def test_plugin_pagure_ticket_deactivate(self):
-        """ Test the pagure_ticket plugin on/off endpoint. """
+        """Test the pagure_ticket plugin on/off endpoint."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             data = {"csrf_token": self.csrf_token}
@@ -131,7 +131,7 @@ class PagureFlaskPluginPagureNoNewBranchHooktests(tests.SimplePagureTest):
             )
 
     def test_plugin_pagure_ticket_activate(self):
-        """ Test the pagure_ticket plugin on/off endpoint. """
+        """Test the pagure_ticket plugin on/off endpoint."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -219,7 +219,7 @@ class PagureFlaskPluginPagureNoNewBranchHooktests(tests.SimplePagureTest):
             )
 
     def test_plugin_pagure_ticket_activate_w_no_repo(self):
-        """ Test the pagure_ticket plugin on/off endpoint. """
+        """Test the pagure_ticket plugin on/off endpoint."""
         shutil.rmtree(os.path.join(self.path, "repos", "test.git"))
 
         user = tests.FakeUser(username="pingou")
diff --git a/tests/test_pagure_flask_ui_plugins_pagure_request_hook.py b/tests/test_pagure_flask_ui_plugins_pagure_request_hook.py
index d3bad06..7316a45 100644
--- a/tests/test_pagure_flask_ui_plugins_pagure_request_hook.py
+++ b/tests/test_pagure_flask_ui_plugins_pagure_request_hook.py
@@ -23,10 +23,10 @@ import tests
 
 
 class PagureFlaskPluginPagureRequestHooktests(tests.SimplePagureTest):
-    """ Tests for pagure_hook plugin of pagure """
+    """Tests for pagure_hook plugin of pagure"""
 
     def test_plugin_pagure_request(self):
-        """ Test the pagure_request plugin on/off endpoint. """
+        """Test the pagure_request plugin on/off endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
diff --git a/tests/test_pagure_flask_ui_plugins_pagure_ticket_hook.py b/tests/test_pagure_flask_ui_plugins_pagure_ticket_hook.py
index ac828a7..6ce2dfb 100644
--- a/tests/test_pagure_flask_ui_plugins_pagure_ticket_hook.py
+++ b/tests/test_pagure_flask_ui_plugins_pagure_ticket_hook.py
@@ -24,10 +24,10 @@ import tests
 
 
 class PagureFlaskPluginPagureTicketHooktests(tests.SimplePagureTest):
-    """ Tests for pagure_hook plugin of pagure """
+    """Tests for pagure_hook plugin of pagure"""
 
     def test_plugin_pagure_ticket(self):
-        """ Test the pagure_ticket plugin on/off endpoint. """
+        """Test the pagure_ticket plugin on/off endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
diff --git a/tests/test_pagure_flask_ui_plugins_rtd_hook.py b/tests/test_pagure_flask_ui_plugins_rtd_hook.py
index 0da4587..a93de35 100644
--- a/tests/test_pagure_flask_ui_plugins_rtd_hook.py
+++ b/tests/test_pagure_flask_ui_plugins_rtd_hook.py
@@ -24,10 +24,10 @@ import tests
 
 
 class PagureFlaskPluginRtdHooktests(tests.SimplePagureTest):
-    """ Tests for rtd_hook plugin of pagure """
+    """Tests for rtd_hook plugin of pagure"""
 
     def test_plugin_pagure_request(self):
-        """ Test the pagure_request plugin on/off endpoint. """
+        """Test the pagure_request plugin on/off endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
diff --git a/tests/test_pagure_flask_ui_plugins_unsigned.py b/tests/test_pagure_flask_ui_plugins_unsigned.py
index c66dfd2..ad50dd9 100644
--- a/tests/test_pagure_flask_ui_plugins_unsigned.py
+++ b/tests/test_pagure_flask_ui_plugins_unsigned.py
@@ -23,10 +23,10 @@ import tests
 
 
 class PagureFlaskPluginUnsignedtests(tests.SimplePagureTest):
-    """ Tests for Block pushes with unsigned commit plugin of pagure """
+    """Tests for Block pushes with unsigned commit plugin of pagure"""
 
     def test_plugin_unsigned(self):
-        """ Test the noff plugin on/off endpoint. """
+        """Test the noff plugin on/off endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
diff --git a/tests/test_pagure_flask_ui_pr_bidi.py b/tests/test_pagure_flask_ui_pr_bidi.py
new file mode 100644
index 0000000..54078b1
--- /dev/null
+++ b/tests/test_pagure_flask_ui_pr_bidi.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+
+"""
+ (c) 2021 - Copyright Red Hat Inc
+
+ Authors:
+   Pierre-Yves Chibon <pingou@pingoured.fr>
+
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+import unittest
+import sys
+import os
+
+import pygit2
+from mock import patch, MagicMock
+
+sys.path.insert(
+    0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
+)
+
+import pagure.lib.query
+import tests
+
+
+class PagureFlaskPrBiditests(tests.Modeltests):
+    """Tests PR in pagure when the PR has bi-directional characters"""
+
+    maxDiff = None
+
+    @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
+    @patch("pagure.lib.notify.fedmsg_publish", MagicMock(return_value=True))
+    def setUp(self):
+        """Set up the environnment, ran before every tests."""
+        super(PagureFlaskPrBiditests, self).setUp()
+
+        tests.create_projects(self.session)
+        tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
+
+        # Create foo's fork of pingou's test project
+        item = pagure.lib.model.Project(
+            user_id=2,  # foo
+            name="test",
+            description="test project #1",
+            hook_token="aaabbb",
+            is_fork=True,
+            parent_id=1,
+        )
+        self.session.add(item)
+        self.session.commit()
+        # Create the fork's git repo
+        repo_path = os.path.join(self.path, "repos", item.path)
+        pygit2.init_repository(repo_path, bare=True)
+
+    def set_up_git_repo(
+        self, repo, fork, branch_from="feature", append_content=None
+    ):
+        """Set up the git repo and create the corresponding PullRequest
+        object.
+        """
+
+        req = tests.add_pull_request_git_repo(
+            self.path,
+            self.session,
+            repo,
+            fork,
+            branch_from,
+            append_content=append_content,
+        )
+
+        self.assertEqual(req.id, 1)
+        self.assertEqual(req.title, "PR from the %s branch" % branch_from)
+
+        tests.clean_pull_requests_path()
+
+    def test_accessing_pr_no_bidi(self):
+        """Test accessing the PR which has no bidi characters."""
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        fork = pagure.lib.query.get_authorized_project(
+            self.session, "test", user="foo"
+        )
+        self.set_up_git_repo(repo=project, fork=fork)
+
+        # Ensure things got setup straight
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        self.assertEqual(len(project.requests), 1)
+
+        # wait for the worker to process the task
+        path = os.path.join(
+            self.path, "repos", "test.git", "refs", "pull", "1", "head"
+        )
+        self.assertTrue(os.path.exists(path))
+
+        # View the pull-request -- no bidi characters found
+        output = self.app.get("/test/pull-request/1")
+        self.assertEqual(output.status_code, 200)
+        self.assertNotIn(
+            "Special characters such as:", output.get_data(as_text=True)
+        )
+
+    def test_accessing_pr_bidi(self):
+        """Test accessing the PR which has no bidi characters."""
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        fork = pagure.lib.query.get_authorized_project(
+            self.session, "test", user="foo"
+        )
+        self.set_up_git_repo(
+            repo=project, fork=fork, append_content="ahah %s" % chr(0x2067)
+        )
+
+        # Ensure things got setup straight
+        project = pagure.lib.query.get_authorized_project(self.session, "test")
+        self.assertEqual(len(project.requests), 1)
+
+        # wait for the worker to process the task
+        path = os.path.join(
+            self.path, "repos", "test.git", "refs", "pull", "1", "head"
+        )
+        self.assertTrue(os.path.exists(path))
+
+        # View the pull-request -- bidi characters found
+        output = self.app.get("/test/pull-request/1")
+        self.assertEqual(output.status_code, 200)
+        self.assertIn(
+            "Special characters such as:", output.get_data(as_text=True)
+        )
+
+
+if __name__ == "__main__":
+    unittest.main(verbosity=2)
diff --git a/tests/test_pagure_flask_ui_pr_edit.py b/tests/test_pagure_flask_ui_pr_edit.py
index af6630b..123b60a 100644
--- a/tests/test_pagure_flask_ui_pr_edit.py
+++ b/tests/test_pagure_flask_ui_pr_edit.py
@@ -10,6 +10,10 @@ from __future__ import unicode_literals, absolute_import
 import sys
 import os
 
+import pagure_messages
+from fedora_messaging import api, testing
+from mock import ANY, patch
+
 sys.path.insert(
     0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
 )
@@ -21,7 +25,7 @@ import pygit2
 
 class PagureFlaskPrEditSimpletests(tests.Modeltests):
     def test_pr_edit_no_project(self):
-        """ Test the edit pull request endpoint """
+        """Test the edit pull request endpoint"""
         output = self.app.get("/foo/pull-request/1/edit")
         self.assertEqual(output.status_code, 404)
         output_text = output.get_data(as_text=True)
@@ -31,7 +35,7 @@ class PagureFlaskPrEditSimpletests(tests.Modeltests):
         self.assertIn("<h2>Page not found (404)</h2>", output_text)
 
     def test_pr_edit_no_git_repo(self):
-        """ Test the edit pull request endpoint """
+        """Test the edit pull request endpoint"""
         tests.create_projects(self.session)
         output = self.app.get("/test/pull-request/1/edit")
         self.assertEqual(output.status_code, 404)
@@ -42,14 +46,14 @@ class PagureFlaskPrEditSimpletests(tests.Modeltests):
         self.assertIn("<p>No git repo found</p>", output_text)
 
     def test_pr_edit_no_pull_requests_no_login(self):
-        """ Test the edit pull request endpoint """
+        """Test the edit pull request endpoint"""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
         output = self.app.get("/test/pull-request/1/edit")
         self.assertEqual(output.status_code, 302)
 
     def test_pr_edit_no_pull_requests(self):
-        """ Test the edit pull request endpoint """
+        """Test the edit pull request endpoint"""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
         user = tests.FakeUser()
@@ -68,6 +72,7 @@ class PagureFlaskPrEdittests(tests.Modeltests):
         super(PagureFlaskPrEdittests, self).setUp()
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
+
         # Create foo's fork of pingou's test project
         item = pagure.lib.model.Project(
             user_id=2,  # foo
@@ -96,6 +101,12 @@ class PagureFlaskPrEdittests(tests.Modeltests):
             allow_rebase=True,
         )
 
+        # Create a "main" branch in addition to the default "master" one
+        repo_obj = pygit2.Repository(
+            os.path.join(self.path, "repos", "test.git")
+        )
+        repo_obj.branches.local.create("main", repo_obj.head.peel())
+
     def tearDown(self):
         try:
             tests.clean_pull_requests_path()
@@ -164,6 +175,7 @@ class PagureFlaskPrEdittests(tests.Modeltests):
             data = {
                 "title": "New title",
                 "initial_comment": "New initial comment",
+                "branch_to": "master",
                 "allow_rebase": False,
             }
             output = self.app.post(
@@ -209,6 +221,9 @@ class PagureFlaskPrEdittests(tests.Modeltests):
             self.assertEqual(None, request.initial_comment)
             self.assertEqual(True, request.allow_rebase)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     def test_pr_edit_pull_request_post_author(self):
         user = tests.FakeUser(username="foo")
         with tests.user_set(self.app.application, user):
@@ -216,11 +231,209 @@ class PagureFlaskPrEdittests(tests.Modeltests):
                 "title": "New title",
                 "initial_comment": "New initial comment",
                 "allow_rebase": False,
+                "branch_to": "master",
                 "csrf_token": self.get_csrf(),
             }
-            output = self.app.post(
-                "/test/pull-request/1/edit", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.PullRequestInitialCommentEditedV1(
+                    topic="pagure.pull-request.initial_comment.edited",
+                    body={
+                        "pullrequest": {
+                            "id": 1,
+                            "uid": ANY,
+                            "title": "New title",
+                            "full_url": "http://localhost.localdomain/test/pull-request/1",
+                            "branch": "master",
+                            "project": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "branch_from": "feature",
+                            "repo_from": {
+                                "id": 4,
+                                "name": "test",
+                                "fullname": "forks/foo/test",
+                                "url_path": "fork/foo/test",
+                                "full_url": "http://localhost.localdomain/fork/foo/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": {
+                                    "id": 1,
+                                    "name": "test",
+                                    "fullname": "test",
+                                    "url_path": "test",
+                                    "description": "test project #1",
+                                    "full_url": "http://localhost.localdomain/test",
+                                    "namespace": None,
+                                    "parent": None,
+                                    "date_created": ANY,
+                                    "date_modified": ANY,
+                                    "user": {
+                                        "name": "pingou",
+                                        "fullname": "PY C",
+                                        "url_path": "user/pingou",
+                                        "full_url": "http://localhost.localdomain/user/pingou",
+                                    },
+                                    "access_users": {
+                                        "owner": ["pingou"],
+                                        "admin": [],
+                                        "commit": [],
+                                        "collaborator": [],
+                                        "ticket": [],
+                                    },
+                                    "access_groups": {
+                                        "admin": [],
+                                        "commit": [],
+                                        "collaborator": [],
+                                        "ticket": [],
+                                    },
+                                    "tags": [],
+                                    "priorities": {},
+                                    "custom_keys": [],
+                                    "close_status": [
+                                        "Invalid",
+                                        "Insufficient data",
+                                        "Fixed",
+                                        "Duplicate",
+                                    ],
+                                    "milestones": {},
+                                },
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "foo",
+                                    "fullname": "foo bar",
+                                    "full_url": "http://localhost.localdomain/user/foo",
+                                    "url_path": "user/foo",
+                                },
+                                "access_users": {
+                                    "owner": ["foo"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [],
+                                "milestones": {},
+                            },
+                            "remote_git": None,
+                            "date_created": ANY,
+                            "updated_on": ANY,
+                            "last_updated": ANY,
+                            "closed_at": None,
+                            "user": {
+                                "name": "foo",
+                                "fullname": "foo bar",
+                                "url_path": "user/foo",
+                                "full_url": "http://localhost.localdomain/user/foo",
+                            },
+                            "assignee": None,
+                            "status": "Open",
+                            "commit_start": None,
+                            "commit_stop": None,
+                            "closed_by": None,
+                            "initial_comment": "New initial comment",
+                            "cached_merge_status": "unknown",
+                            "threshold_reached": None,
+                            "tags": [],
+                            "comments": [],
+                        },
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "url_path": "test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "agent": "foo",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/pull-request/1/edit",
+                    data=data,
+                    follow_redirects=True,
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             # After successful edit, we end on pull_request view with new data
@@ -288,6 +501,74 @@ class PagureFlaskPrEdittests(tests.Modeltests):
                 "title": "New title",
                 "initial_comment": "New initial comment",
                 "allow_rebase": False,
+                "branch_to": "master",
+                "csrf_token": self.get_csrf(),
+            }
+            output = self.app.post(
+                "/test/pull-request/1/edit", data=data, follow_redirects=True
+            )
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            # After successful edit, we end on pull_request view with new data
+            self.assertIn(
+                "<title>PR#1: New title - test\n - Pagure</title>", output_text
+            )
+            self.assertIn(
+                '<span class="font-weight-bold">\n'
+                "                  New title\n"
+                "            </span>",
+                output_text,
+            )
+            self.assertIn("<p>New initial comment</p>", output_text)
+            request = pagure.lib.query.search_pull_requests(
+                self.session, project_id=1, requestid=1
+            )
+            # DB model has been changed
+            self.assertEqual("New title", request.title)
+            self.assertEqual("New initial comment", request.initial_comment)
+            # But allow_rebase remains unchanged
+            self.assertEqual(True, request.allow_rebase)
+
+    def test_pr_edit_pull_request_invalid_branch_to(self):
+        user = tests.FakeUser(username="pingou")
+        with tests.user_set(self.app.application, user):
+            data = {
+                "title": "New title",
+                "initial_comment": "New initial comment",
+                "allow_rebase": False,
+                "branch_to": "invalid",
+                "csrf_token": self.get_csrf(),
+            }
+            output = self.app.post(
+                "/test/pull-request/1/edit", data=data, follow_redirects=True
+            )
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            # Edit failed - we're back on the same page
+            self.assertIn(
+                "<title>Edit PR#1: PR from the feature branch - test - Pagure"
+                "</title>",
+                output_text,
+            )
+            self.assertIn("Not a valid choice", output_text)
+            request = pagure.lib.query.search_pull_requests(
+                self.session, project_id=1, requestid=1
+            )
+            # DB model has not been changed
+            self.assertEqual("PR from the feature branch", request.title)
+            self.assertEqual(None, request.initial_comment)
+            self.assertEqual("master", request.branch)
+            # But allow_rebase remains unchanged
+            self.assertEqual(True, request.allow_rebase)
+
+    def test_pr_edit_pull_request_valid_branch_to(self):
+        user = tests.FakeUser(username="pingou")
+        with tests.user_set(self.app.application, user):
+            data = {
+                "title": "New title",
+                "initial_comment": "New initial comment",
+                "allow_rebase": False,
+                "branch_to": "main",
                 "csrf_token": self.get_csrf(),
             }
             output = self.app.post(
@@ -312,5 +593,6 @@ class PagureFlaskPrEdittests(tests.Modeltests):
             # DB model has been changed
             self.assertEqual("New title", request.title)
             self.assertEqual("New initial comment", request.initial_comment)
+            self.assertEqual("main", request.branch)
             # But allow_rebase remains unchanged
             self.assertEqual(True, request.allow_rebase)
diff --git a/tests/test_pagure_flask_ui_pr_no_sources.py b/tests/test_pagure_flask_ui_pr_no_sources.py
index 4040043..4ea492f 100644
--- a/tests/test_pagure_flask_ui_pr_no_sources.py
+++ b/tests/test_pagure_flask_ui_pr_no_sources.py
@@ -31,14 +31,14 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureFlaskPrNoSourcestests(tests.Modeltests):
-    """ Tests PR in pagure when the source is gone """
+    """Tests PR in pagure when the source is gone"""
 
     maxDiff = None
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     @patch("pagure.lib.notify.fedmsg_publish", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskPrNoSourcestests, self).setUp()
 
         tests.create_projects(self.session)
@@ -77,7 +77,7 @@ class PagureFlaskPrNoSourcestests(tests.Modeltests):
         self.assertTrue(os.path.exists(path))
 
     def set_up_git_repo(self, repo, fork, branch_from="feature"):
-        """ Set up the git repo and create the corresponding PullRequest
+        """Set up the git repo and create the corresponding PullRequest
         object.
         """
 
@@ -91,7 +91,7 @@ class PagureFlaskPrNoSourcestests(tests.Modeltests):
         tests.clean_pull_requests_path()
 
     def test_request_pull_reference(self):
-        """ Test if there is a reference created for a new PR. """
+        """Test if there is a reference created for a new PR."""
 
         project = pagure.lib.query.get_authorized_project(self.session, "test")
         self.assertEqual(len(project.requests), 1)
@@ -104,7 +104,7 @@ class PagureFlaskPrNoSourcestests(tests.Modeltests):
         )
 
     def test_request_pull_fork_reference(self):
-        """ Test if there the references created on the fork. """
+        """Test if there the references created on the fork."""
 
         project = pagure.lib.query.get_authorized_project(
             self.session, "test", user="foo"
@@ -118,7 +118,7 @@ class PagureFlaskPrNoSourcestests(tests.Modeltests):
         )
 
     def test_accessing_pr_fork_deleted(self):
-        """ Test accessing the PR if the fork has been deleted. """
+        """Test accessing the PR if the fork has been deleted."""
 
         # Delete fork on disk
         project = pagure.lib.query.get_authorized_project(
@@ -138,7 +138,7 @@ class PagureFlaskPrNoSourcestests(tests.Modeltests):
         self.assertEqual(output2.status_code, 200)
 
     def test_accessing_pr_patch_fork_deleted(self):
-        """ Test accessing the PR's patch if the fork has been deleted. """
+        """Test accessing the PR's patch if the fork has been deleted."""
 
         # Delete fork on disk
         project = pagure.lib.query.get_authorized_project(
@@ -162,8 +162,8 @@ class PagureFlaskPrNoSourcestests(tests.Modeltests):
         )
 
     def test_accessing_pr_branch_deleted(self):
-        """ Test accessing the PR if branch it originates from has been
-        deleted. """
+        """Test accessing the PR if branch it originates from has been
+        deleted."""
         project = pagure.lib.query.get_authorized_project(
             self.session, "test", user="foo"
         )
@@ -193,8 +193,8 @@ class PagureFlaskPrNoSourcestests(tests.Modeltests):
         self.assertEqual(output2.status_code, 200)
 
     def test_accessing_pr_patch_branch_deleted(self):
-        """ Test accessing the PR's patch if branch it originates from has
-        been deleted. """
+        """Test accessing the PR's patch if branch it originates from has
+        been deleted."""
         project = pagure.lib.query.get_authorized_project(
             self.session, "test", user="foo"
         )
diff --git a/tests/test_pagure_flask_ui_priorities.py b/tests/test_pagure_flask_ui_priorities.py
index bca2239..4c0f7e8 100644
--- a/tests/test_pagure_flask_ui_priorities.py
+++ b/tests/test_pagure_flask_ui_priorities.py
@@ -31,12 +31,12 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureFlaskPrioritiestests(tests.Modeltests):
-    """ Tests for the behavior of priorities in pagure """
+    """Tests for the behavior of priorities in pagure"""
 
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_ticket_with_no_priority(self, p_send_email, p_ugt):
-        """ Test creating a ticket without priority. """
+        """Test creating a ticket without priority."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -90,7 +90,7 @@ class PagureFlaskPrioritiestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_ticket_with_priorities(self, p_send_email, p_ugt):
-        """ Test creating a ticket with priorities. """
+        """Test creating a ticket with priorities."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -148,7 +148,7 @@ class PagureFlaskPrioritiestests(tests.Modeltests):
             self.assertIn('<option value="1">High</option>', output_text)
 
     def test_update_priorities(self):
-        """ Test updating priorities of a repo. """
+        """Test updating priorities of a repo."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -416,7 +416,7 @@ class PagureFlaskPrioritiestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_reset_priorities(self, p_send_email, p_ugt):
-        """ Test resetting the priorities of a repo. """
+        """Test resetting the priorities of a repo."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -549,7 +549,7 @@ class PagureFlaskPrioritiestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_reset_priorities_None(self, p_send_email, p_ugt):
-        """ Test resetting the priorities of a repo. """
+        """Test resetting the priorities of a repo."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -682,7 +682,7 @@ class PagureFlaskPrioritiestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_set_priority_1_and_back(self):
-        """ Test setting the priority of a ticket to 1. """
+        """Test setting the priority of a ticket to 1."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -850,7 +850,7 @@ class PagureFlaskPrioritiestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_set_priority_0(self):
-        """ Test setting the priority of a ticket to 0. """
+        """Test setting the priority of a ticket to 0."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -992,7 +992,7 @@ class PagureFlaskPrioritiestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_set_priority_minus1(self):
-        """ Test setting the priority of a ticket to -1. """
+        """Test setting the priority of a ticket to -1."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -1132,7 +1132,7 @@ class PagureFlaskPrioritiestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_default_priority(self):
-        """ Test updating the default priority of a repo. """
+        """Test updating the default priority of a repo."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -1315,7 +1315,7 @@ class PagureFlaskPrioritiestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_default_priority_reset_when_updating_priorities(self):
-        """ Test updating the default priority of a repo when updating the
+        """Test updating the default priority of a repo when updating the
         priorities.
         """
         tests.create_projects(self.session)
@@ -1439,7 +1439,7 @@ class PagureFlaskPrioritiestests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_default_priority_on_new_ticket(self):
-        """ Test updating the default priority of a repo. """
+        """Test updating the default priority of a repo."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
diff --git a/tests/test_pagure_flask_ui_quick_reply.py b/tests/test_pagure_flask_ui_quick_reply.py
index 6831ee8..a2d984f 100644
--- a/tests/test_pagure_flask_ui_quick_reply.py
+++ b/tests/test_pagure_flask_ui_quick_reply.py
@@ -29,10 +29,10 @@ import tests
 
 
 class PagureFlaskQuickReplytest(tests.Modeltests):
-    """ Tests for configuring and displaying quick replies. """
+    """Tests for configuring and displaying quick replies."""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskQuickReplytest, self).setUp()
 
         tests.create_projects(self.session)
diff --git a/tests/test_pagure_flask_ui_remote_pr.py b/tests/test_pagure_flask_ui_remote_pr.py
index ad23a0c..c779a06 100644
--- a/tests/test_pagure_flask_ui_remote_pr.py
+++ b/tests/test_pagure_flask_ui_remote_pr.py
@@ -35,10 +35,10 @@ from pagure.lib.git import _make_signature
 
 
 class PagureRemotePRtests(tests.Modeltests):
-    """ Tests for remote PRs in pagure """
+    """Tests for remote PRs in pagure"""
 
     def setUp(self):
-        """ Set up the environment. """
+        """Set up the environment."""
         super(PagureRemotePRtests, self).setUp()
 
         self.newpath = tempfile.mkdtemp(prefix="pagure-fork-test")
@@ -48,14 +48,14 @@ class PagureRemotePRtests(tests.Modeltests):
         )
 
     def tearDown(self):
-        """ Clear things up. """
+        """Clear things up."""
         super(PagureRemotePRtests, self).tearDown()
 
         pagure.config.config["REMOTE_GIT_FOLDER"] = self.old_value
         shutil.rmtree(self.newpath)
 
     def set_up_git_repo(self, new_project=None, branch_from="feature"):
-        """ Set up the git repo and create the corresponding PullRequest
+        """Set up the git repo and create the corresponding PullRequest
         object.
         """
 
@@ -158,7 +158,7 @@ class PagureRemotePRtests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_new_remote_pr_unauth(self):
-        """ Test creating a new remote PR un-authenticated. """
+        """Test creating a new remote PR un-authenticated."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(
@@ -181,7 +181,7 @@ class PagureRemotePRtests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_new_remote_pr_auth(self):
-        """ Test creating a new remote PR authenticated. """
+        """Test creating a new remote PR authenticated."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(
@@ -293,8 +293,8 @@ class PagureRemotePRtests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_new_remote_no_title(self):
-        """ Test creating a new remote PR authenticated when no title is
-        specified. """
+        """Test creating a new remote PR authenticated when no title is
+        specified."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(
@@ -341,8 +341,8 @@ class PagureRemotePRtests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_new_remote_pr_empty_target(self):
-        """ Test creating a new remote PR authenticated against an empty
-        git repo. """
+        """Test creating a new remote PR authenticated against an empty
+        git repo."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(
@@ -498,7 +498,7 @@ class PagureRemotePRtests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     @patch("pagure.lib.tasks_services.trigger_ci_build")
     def test_new_remote_pr_ci_off(self, trigger_ci):
-        """ Test creating a new remote PR when CI is not configured. """
+        """Test creating a new remote PR when CI is not configured."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(
@@ -563,7 +563,7 @@ class PagureRemotePRtests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     @patch("pagure.lib.tasks_services.trigger_ci_build")
     def test_new_remote_pr_ci_on(self, trigger_ci):
-        """ Test creating a new remote PR when CI is configured. """
+        """Test creating a new remote PR when CI is configured."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(
diff --git a/tests/test_pagure_flask_ui_repo.py b/tests/test_pagure_flask_ui_repo.py
index 27bf6e6..9603726 100644
--- a/tests/test_pagure_flask_ui_repo.py
+++ b/tests/test_pagure_flask_ui_repo.py
@@ -20,8 +20,16 @@ import tempfile
 import time
 import os
 
+cchardet = None
+try:
+    import cchardet
+except ImportError:
+    pass
+
+import pagure_messages
 import pygit2
 import six
+from fedora_messaging import testing
 from mock import ANY, patch, MagicMock
 
 sys.path.insert(
@@ -35,10 +43,10 @@ from pagure.utils import __get_file_in_tree as get_file_in_tree
 
 
 class PagureFlaskRepotests(tests.Modeltests):
-    """ Tests for flask app controller of pagure """
+    """Tests for flask app controller of pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskRepotests, self).setUp()
 
         pagure.config.config["VIRUS_SCAN_ATTACHMENTS"] = False
@@ -49,8 +57,8 @@ class PagureFlaskRepotests(tests.Modeltests):
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_add_user_when_user_mngt_off(self, ast):
-        """ Test the add_user endpoint when user management is turned off
-        in the pagure instance """
+        """Test the add_user endpoint when user management is turned off
+        in the pagure instance"""
         pagure.config.config["ENABLE_USER_MNGT"] = False
         ast.return_value = False
 
@@ -101,7 +109,7 @@ class PagureFlaskRepotests(tests.Modeltests):
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_add_deploykey(self, ast):
-        """ Test the add_deploykey endpoint. """
+        """Test the add_deploykey endpoint."""
         ast.return_value = False
 
         # No git repo
@@ -226,8 +234,7 @@ class PagureFlaskRepotests(tests.Modeltests):
     @patch("pagure.decorators.admin_session_timedout")
     @patch.dict("pagure.config.config", {"DEPLOY_KEY": False})
     def test_add_deploykey_disabled(self, ast):
-        """ Test the add_deploykey endpoint when it's disabled in the config.
-        """
+        """Test the add_deploykey endpoint when it's disabled in the config."""
         ast.return_value = False
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
@@ -240,10 +247,12 @@ class PagureFlaskRepotests(tests.Modeltests):
             output = self.app.post("/test/adddeploykey")
             self.assertEqual(output.status_code, 404)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.decorators.admin_session_timedout")
-    @patch("pagure.lib.notify.log")
-    def test_add_user(self, mock_log, ast):
-        """ Test the add_user endpoint. """
+    def test_add_user(self, ast):
+        """Test the add_user endpoint."""
         ast.return_value = False
 
         # No git repo
@@ -332,9 +341,61 @@ class PagureFlaskRepotests(tests.Modeltests):
 
             # All correct
             data["user"] = "foo"
-            output = self.app.post(
-                "/test/adduser", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.ProjectUserAddedV1(
+                    topic="pagure.project.user.added",
+                    body={
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": ["foo"],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "new_user": "foo",
+                        "access": "commit",
+                        "branches": None,
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/adduser", data=data, follow_redirects=True
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -343,11 +404,75 @@ class PagureFlaskRepotests(tests.Modeltests):
             )
             self.assertIn("User added", output_text)
 
-        mock_log.assert_called_with(ANY, topic="project.user.added", msg=ANY)
+            # Update access
+            data["access"] = "ticket"
+            data["user"] = "foo"
+            with testing.mock_sends(
+                pagure_messages.ProjectUserAccessUpdatedV1(
+                    topic="pagure.project.user.access.updated",
+                    body={
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": ["foo"],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "new_user": "foo",
+                        "new_access": "ticket",
+                        "new_branches": None,
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/adduser", data=data, follow_redirects=True
+                )
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                '<h5 class="pl-2 font-weight-bold text-muted">Project Settings</h5>',
+                output_text,
+            )
+            self.assertIn("User access updated", output_text)
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_add_group_project_when_user_mngt_off(self, ast):
-        """ Test the add_group_project endpoint  when user management is
+        """Test the add_group_project endpoint  when user management is
         turned off in the pagure instance"""
         pagure.config.config["ENABLE_USER_MNGT"] = False
         ast.return_value = False
@@ -408,7 +533,7 @@ class PagureFlaskRepotests(tests.Modeltests):
     @patch.dict("pagure.config.config", {"ENABLE_GROUP_MNGT": False})
     @patch("pagure.decorators.admin_session_timedout")
     def test_add_group_project_grp_mngt_off(self, ast):
-        """ Test the add_group_project endpoint  when group management is
+        """Test the add_group_project endpoint  when group management is
         turned off in the pagure instance"""
         ast.return_value = False
 
@@ -433,9 +558,12 @@ class PagureFlaskRepotests(tests.Modeltests):
             )
             self.assertIn("No group ralph found.", output_text)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.decorators.admin_session_timedout")
     def test_add_group_project(self, ast):
-        """ Test the add_group_project endpoint. """
+        """Test the add_group_project endpoint."""
         ast.return_value = False
 
         # No Git repo
@@ -518,9 +646,61 @@ class PagureFlaskRepotests(tests.Modeltests):
 
             # All good
             data["access"] = "ticket"
-            output = self.app.post(
-                "/test/addgroup", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.ProjectGroupAddedV1(
+                    topic="pagure.project.group.added",
+                    body={
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": ["ralph"],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "new_group": "ralph",
+                        "access": "ticket",
+                        "branches": None,
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/addgroup", data=data, follow_redirects=True
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -532,9 +712,77 @@ class PagureFlaskRepotests(tests.Modeltests):
             )
             self.assertIn("Group added", output_text)
 
+            # All good -- Update existing group/access
+            data["access"] = "commit"
+            with testing.mock_sends(
+                pagure_messages.ProjectGroupAccessUpdatedV1(
+                    topic="pagure.project.group.access.updated",
+                    body={
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": ["ralph"],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "new_group": "ralph",
+                        "new_access": "commit",
+                        "new_branches": None,
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/addgroup", data=data, follow_redirects=True
+                )
+            self.assertEqual(output.status_code, 200)
+            output_text = output.get_data(as_text=True)
+            self.assertIn(
+                "<title>Settings - test - Pagure</title>", output_text
+            )
+            self.assertIn(
+                '<h5 class="pl-2 font-weight-bold text-muted">Project Settings</h5>',
+                output_text,
+            )
+            self.assertIn("Group access updated", output_text)
+
     @patch("pagure.decorators.admin_session_timedout")
     def test_remove_user_when_user_mngt_off(self, ast):
-        """ Test the remove_user endpoint when user management is turned
+        """Test the remove_user endpoint when user management is turned
         off in the pagure instance"""
         pagure.config.config["ENABLE_USER_MNGT"] = False
         ast.return_value = False
@@ -579,6 +827,7 @@ class PagureFlaskRepotests(tests.Modeltests):
             self.assertEqual(output.status_code, 404)
 
             data = {"csrf_token": csrf_token}
+
             output = self.app.post(
                 "/test/dropuser/2", data=data, follow_redirects=True
             )
@@ -588,7 +837,7 @@ class PagureFlaskRepotests(tests.Modeltests):
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_remove_deploykey(self, ast):
-        """ Test the remove_deploykey endpoint. """
+        """Test the remove_deploykey endpoint."""
         ast.return_value = False
 
         # Git repo not found
@@ -686,7 +935,7 @@ class PagureFlaskRepotests(tests.Modeltests):
     @patch("pagure.decorators.admin_session_timedout")
     @patch.dict("pagure.config.config", {"DEPLOY_KEY": False})
     def test_remove_deploykey_disabled(self, ast):
-        """ Test the remove_deploykey endpoint when it's disabled in the
+        """Test the remove_deploykey endpoint when it's disabled in the
         config.
         """
         ast.return_value = False
@@ -698,10 +947,12 @@ class PagureFlaskRepotests(tests.Modeltests):
             output = self.app.post("/test/dropdeploykey/1")
             self.assertEqual(output.status_code, 404)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.decorators.admin_session_timedout")
-    @patch("pagure.lib.notify.log")
-    def test_remove_user(self, mock_log, ast):
-        """ Test the remove_user endpoint. """
+    def test_remove_user(self, ast):
+        """Test the remove_user endpoint."""
         ast.return_value = False
 
         # Git repo not found
@@ -784,9 +1035,60 @@ class PagureFlaskRepotests(tests.Modeltests):
             self.assertEqual(len(repo.users), 1)
 
             data = {"csrf_token": csrf_token}
-            output = self.app.post(
-                "/test/dropuser/2", data=data, follow_redirects=True
-            )
+
+            with testing.mock_sends(
+                pagure_messages.ProjectUserRemovedV1(
+                    topic="pagure.project.user.removed",
+                    body={
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "removed_user": "foo",
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/dropuser/2", data=data, follow_redirects=True
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -805,12 +1107,10 @@ class PagureFlaskRepotests(tests.Modeltests):
             )
             self.assertEqual(len(repo.users), 0)
 
-        mock_log.assert_called_with(ANY, topic="project.user.removed", msg=ANY)
-
     @patch("pagure.decorators.admin_session_timedout")
     @patch("pagure.lib.notify.log")
     def test_remove_user_self(self, mock_log, ast):
-        """ Test the remove_user endpoint when removing themselves. """
+        """Test the remove_user endpoint when removing themselves."""
         ast.return_value = False
 
         tests.create_projects(self.session)
@@ -855,7 +1155,7 @@ class PagureFlaskRepotests(tests.Modeltests):
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_remove_group_project_when_user_mngt_off(self, ast):
-        """ Test the remove_group_project endpoint when user management is
+        """Test the remove_group_project endpoint when user management is
         turned off in the pagure instance"""
         pagure.config.config["ENABLE_USER_MNGT"] = False
         ast.return_value = False
@@ -925,9 +1225,12 @@ class PagureFlaskRepotests(tests.Modeltests):
 
         pagure.config.config["ENABLE_USER_MNGT"] = True
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.decorators.admin_session_timedout")
     def test_remove_group_project(self, ast):
-        """ Test the remove_group_project endpoint. """
+        """Test the remove_group_project endpoint."""
         ast.return_value = False
 
         # No Git repo
@@ -1029,9 +1332,59 @@ class PagureFlaskRepotests(tests.Modeltests):
             self.assertEqual(len(repo.groups), 1)
 
             data = {"csrf_token": csrf_token}
-            output = self.app.post(
-                "/test/dropgroup/1", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.ProjectGroupRemovedV1(
+                    topic="pagure.project.group.removed",
+                    body={
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "removed_groups": ["testgrp"],
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/dropgroup/1", data=data, follow_redirects=True
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -1052,7 +1405,7 @@ class PagureFlaskRepotests(tests.Modeltests):
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_update_project(self, ast):
-        """ Test the update_project endpoint. """
+        """Test the update_project endpoint."""
         ast.return_value = True
 
         # Git repo not found
@@ -1171,7 +1524,7 @@ class PagureFlaskRepotests(tests.Modeltests):
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_update_project_update_tag(self, ast):
-        """ Test the view_settings endpoint when updating the project's tags.
+        """Test the view_settings endpoint when updating the project's tags.
 
         We had an issue where when you add an existing tag to a project we
         were querying the wrong table in the database. It would thus not find
@@ -1264,9 +1617,12 @@ class PagureFlaskRepotests(tests.Modeltests):
             )
             self.assertIn("Project updated", output_text)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch("pagure.decorators.admin_session_timedout")
     def test_view_settings(self, ast):
-        """ Test the view_settings endpoint. """
+        """Test the view_settings endpoint."""
         ast.return_value = False
 
         # No Git repo
@@ -1323,6 +1679,7 @@ class PagureFlaskRepotests(tests.Modeltests):
             )[1].split('">')[0]
 
             data = {}
+
             output = self.app.post(
                 "/test/settings", data=data, follow_redirects=True
             )
@@ -1359,6 +1716,7 @@ class PagureFlaskRepotests(tests.Modeltests):
             )
 
             data = {"csrf_token": csrf_token}
+
             output = self.app.post(
                 "/test/settings", data=data, follow_redirects=True
             )
@@ -1397,10 +1755,65 @@ class PagureFlaskRepotests(tests.Modeltests):
                 "csrf_token": csrf_token,
                 "pull_requests": "y",
                 "issue_tracker": "y",
+                "fedmsg_notifications": "y",
             }
-            output = self.app.post(
-                "/test/settings", data=data, follow_redirects=True
-            )
+            with testing.mock_sends(
+                pagure_messages.ProjectEditV1(
+                    topic="pagure.project.edit",
+                    body={
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "fields": [
+                            "fedmsg_notifications",
+                            "issue_tracker",
+                            "pull_requests",
+                        ],
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/test/settings", data=data, follow_redirects=True
+                )
             self.assertEqual(output.status_code, 200)
             output_text = output.get_data(as_text=True)
             self.assertIn(
@@ -1437,8 +1850,8 @@ class PagureFlaskRepotests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_view_settings_custom_fields(self):
-        """ Test the view_settings endpoint when the project has some custom
-        field for issues. """
+        """Test the view_settings endpoint when the project has some custom
+        field for issues."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
@@ -1470,7 +1883,7 @@ class PagureFlaskRepotests(tests.Modeltests):
     @patch("pagure.lib.git.generate_gitolite_acls")
     @patch("pagure.decorators.admin_session_timedout")
     def test_view_settings_pr_only(self, ast, gen_acl):
-        """ Test the view_settings endpoint when turning on PR only. """
+        """Test the view_settings endpoint when turning on PR only."""
         ast.return_value = False
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
@@ -1546,7 +1959,7 @@ class PagureFlaskRepotests(tests.Modeltests):
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_fields_in_view_settings(self, ast):
-        """ Test the default fields in view_settings endpoint. """
+        """Test the default fields in view_settings endpoint."""
         ast.return_value = False
 
         # No Git repo
@@ -1671,7 +2084,7 @@ class PagureFlaskRepotests(tests.Modeltests):
             )
 
     def test_view_forks(self):
-        """ Test the view_forks endpoint. """
+        """Test the view_forks endpoint."""
 
         output = self.app.get("/foo/forks", follow_redirects=True)
         self.assertEqual(output.status_code, 404)
@@ -1686,7 +2099,7 @@ class PagureFlaskRepotests(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"CASE_SENSITIVE": True})
     def test_view_repo_case_sensitive(self):
-        """ Test the view_repo endpoint. """
+        """Test the view_repo endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -1700,8 +2113,8 @@ class PagureFlaskRepotests(tests.Modeltests):
         self.assertEqual(output.status_code, 404)
 
     def test_view_repo_more_button_absent_no_auth(self):
-        """ Test the view_repo endpoint and check if the "more" button is
-        absent when not logged in. """
+        """Test the view_repo endpoint and check if the "more" button is
+        absent when not logged in."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -1723,8 +2136,8 @@ class PagureFlaskRepotests(tests.Modeltests):
         self.perfReset()
 
     def test_view_repo_more_button_present(self):
-        """ Test the view_repo endpoint and check if the "more" button is
-        present when it should be. """
+        """Test the view_repo endpoint and check if the "more" button is
+        present when it should be."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -1764,8 +2177,8 @@ class PagureFlaskRepotests(tests.Modeltests):
             self.perfReset()
 
     def test_view_repo_more_button_absent_no_access(self):
-        """ Test the view_repo endpoint and check if the "more" button is
-        absent if the user doesn't have access to the project. """
+        """Test the view_repo endpoint and check if the "more" button is
+        absent if the user doesn't have access to the project."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -1791,8 +2204,8 @@ class PagureFlaskRepotests(tests.Modeltests):
             self.perfReset()
 
     def test_view_repo_ssh_key_not_uploaded_no_ssh_url(self):
-        """ Test viewing repo when user hasn't uploaded SSH key yet
-        and thus should see a message instead of url for SSH cloning. """
+        """Test viewing repo when user hasn't uploaded SSH key yet
+        and thus should see a message instead of url for SSH cloning."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
         user = tests.FakeUser(username="pingou")
@@ -1807,8 +2220,8 @@ class PagureFlaskRepotests(tests.Modeltests):
             )
 
     def test_view_repo_read_only_no_ssh_url(self):
-        """ Test viewing repo that is still readonly and thus user
-        should see a message instead of url for SSH cloning. """
+        """Test viewing repo that is still readonly and thus user
+        should see a message instead of url for SSH cloning."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -1833,7 +2246,7 @@ class PagureFlaskRepotests(tests.Modeltests):
             self.assertIn("Cloning over SSH is disabled.", output_text)
 
     def test_view_repo(self):
-        """ Test the view_repo endpoint. """
+        """Test the view_repo endpoint."""
 
         output = self.app.get("/foo")
         # No project registered in the DB
@@ -2031,7 +2444,7 @@ class PagureFlaskRepotests(tests.Modeltests):
         self.perfReset()
 
     def test_view_repo_empty(self):
-        """ Test the view_repo endpoint on a repo w/o master branch. """
+        """Test the view_repo endpoint on a repo w/o master branch."""
 
         tests.create_projects(self.session)
         # Create a git repo to play with
@@ -2145,7 +2558,7 @@ class PagureFlaskRepotests(tests.Modeltests):
     '''
 
     def test_view_commits(self):
-        """ Test the view_commits endpoint. """
+        """Test the view_commits endpoint."""
         output = self.app.get("/foo/commits")
         # No project registered in the DB
         self.assertEqual(output.status_code, 404)
@@ -2253,7 +2666,7 @@ class PagureFlaskRepotests(tests.Modeltests):
         self.assertIn("Forked from", output_text)
 
     def test_view_commits_from_tag(self):
-        """ Test the view_commits endpoint given a tag. """
+        """Test the view_commits endpoint given a tag."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -2284,7 +2697,7 @@ class PagureFlaskRepotests(tests.Modeltests):
         self.assertEqual(output_text.count('<span id="commit-actions">'), 1)
 
     def test_view_commits_from_blob(self):
-        """ Test the view_commits endpoint given a blob. """
+        """Test the view_commits endpoint given a blob."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -2306,7 +2719,7 @@ class PagureFlaskRepotests(tests.Modeltests):
         self.assertIn("Invalid branch/identifier provided", output_text)
 
     def test_view_commit_from_tag(self):
-        """ Test the view_commit endpoint given a tag. """
+        """Test the view_commit endpoint given a tag."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -2344,7 +2757,7 @@ class PagureFlaskRepotests(tests.Modeltests):
         )
 
     def test_compare_commits(self):
-        """ Test the compare_commits endpoint. """
+        """Test the compare_commits endpoint."""
 
         # First two commits comparison
         def compare_first_two(c1, c2):
@@ -2414,7 +2827,7 @@ class PagureFlaskRepotests(tests.Modeltests):
             )
             self.assertIn('<a class="pointer">1 more commits...', output_text)
             self.assertIn(
-                'title="View file as of 4829cf">Šource</a>', output_text
+                'title="View file as of 4829cfa">Šource</a>', output_text
             )
             self.assertIn(
                 '<div class="btn btn-outline-success disabled opacity-100 border-0 font-weight-bold">\n'
@@ -2446,7 +2859,7 @@ class PagureFlaskRepotests(tests.Modeltests):
             )
             self.assertIn('<a class="pointer">1 more commits...', output_text)
             self.assertIn(
-                'title="View file as of 000000">Šource</a>', output_text
+                'title="View file as of 0000000">Šource</a>', output_text
             )
             self.assertIn(
                 '<div class="btn btn-outline-danger disabled opacity-100 border-0 font-weight-bold">\n'
@@ -2529,7 +2942,7 @@ class PagureFlaskRepotests(tests.Modeltests):
             compare_with_symlink(c3, c4)
 
     def test_view_file(self):
-        """ Test the view_file endpoint. """
+        """Test the view_file endpoint."""
         output = self.app.get("/foo/blob/foo/f/sources")
         # No project registered in the DB
         self.assertEqual(output.status_code, 404)
@@ -2592,12 +3005,7 @@ class PagureFlaskRepotests(tests.Modeltests):
         # View what's supposed to be an image
         output = self.app.get("/test/blob/master/f/test.jpg")
         self.assertEqual(output.status_code, 200)
-        output_text = output.get_data(as_text=True)
-        self.assertIn("Binary files cannot be rendered.<br/>", output_text)
-        self.assertIn(
-            '<a href="/test/raw/master/f/test.jpg">view the raw version',
-            output_text,
-        )
+        self.assertNotIn(b"<html", output.data)
 
         # View by commit id
         repo = pygit2.Repository(os.path.join(self.path, "repos", "test.git"))
@@ -2605,23 +3013,17 @@ class PagureFlaskRepotests(tests.Modeltests):
 
         output = self.app.get("/test/blob/%s/f/test.jpg" % commit.oid.hex)
         self.assertEqual(output.status_code, 200)
-        output_text = output.get_data(as_text=True)
-        self.assertIn("Binary files cannot be rendered.<br/>", output_text)
-        self.assertIn('/f/test.jpg">view the raw version', output_text)
+        self.assertNotIn(b"<html", output.data)
 
         # View by image name -- somehow we support this
-        output = self.app.get("/test/blob/sources/f/test.jpg")
+        output = self.app.get("/test/blob/master/f/test.jpg")
         self.assertEqual(output.status_code, 200)
-        output_text = output.get_data(as_text=True)
-        self.assertIn("Binary files cannot be rendered.<br/>", output_text)
-        self.assertIn('/f/test.jpg">view the raw version', output_text)
+        self.assertNotIn(b"<html", output.data)
 
         # View binary file
-        output = self.app.get("/test/blob/sources/f/test_binary")
+        output = self.app.get("/test/blob/master/f/test_binary")
         self.assertEqual(output.status_code, 200)
-        output_text = output.get_data(as_text=True)
-        self.assertIn('/f/test_binary">view the raw version', output_text)
-        self.assertIn("Binary files cannot be rendered.<br/>", output_text)
+        self.assertNotIn(b"<html", output.data)
 
         # View folder
         output = self.app.get("/test/blob/master/f/folder1")
@@ -2727,7 +3129,7 @@ class PagureFlaskRepotests(tests.Modeltests):
         MagicMock(side_effect=pagure.exceptions.PagureException),
     )
     def test_view_file_with_wrong_encoding(self):
-        """ Test the view_file endpoint. """
+        """Test the view_file endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -2748,10 +3150,10 @@ class PagureFlaskRepotests(tests.Modeltests):
         output = self.app.get("/test/blob/master/f/sources")
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
-        self.assertIn("Binary files cannot be rendered.<br/>", output_text)
+        self.assertEqual("foo\n bar", output_text)
 
     def test_view_raw_file(self):
-        """ Test the view_raw_file endpoint. """
+        """Test the view_raw_file endpoint."""
         output = self.app.get("/foo/raw/foo/sources")
         # No project registered in the DB
         self.assertEqual(output.status_code, 404)
@@ -2775,7 +3177,8 @@ class PagureFlaskRepotests(tests.Modeltests):
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
         self.assertEqual(
-            output.headers["Content-Type"].lower(), "text/plain; charset=ascii"
+            output.headers["Content-Type"].lower(),
+            "text/plain; charset=ascii",
         )
         self.assertIn(":Author: Pierre-Yves Chibon", output_text)
 
@@ -2796,7 +3199,8 @@ class PagureFlaskRepotests(tests.Modeltests):
         # View in a branch
         output = self.app.get("/test/raw/master/f/sources")
         self.assertEqual(
-            output.headers["Content-Type"].lower(), "text/plain; charset=ascii"
+            output.headers["Content-Type"].lower(),
+            "text/plain; charset=ascii",
         )
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
@@ -2849,7 +3253,8 @@ class PagureFlaskRepotests(tests.Modeltests):
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
         self.assertEqual(
-            output.headers["Content-Type"].lower(), "text/plain; charset=ascii"
+            output.headers["Content-Type"].lower(),
+            "text/plain; charset=ascii",
         )
         self.assertTrue(
             output_text.startswith("diff --git a/test_binary b/test_binary\n")
@@ -2889,12 +3294,13 @@ class PagureFlaskRepotests(tests.Modeltests):
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
         self.assertEqual(
-            output.headers["Content-Type"].lower(), "text/plain; charset=ascii"
+            output.headers["Content-Type"].lower(),
+            "text/plain; charset=ascii",
         )
         self.assertIn("foo\n bar", output_text)
 
     def test_view_commit(self):
-        """ Test the view_commit endpoint. """
+        """Test the view_commit endpoint."""
         output = self.app.get("/foo/c/bar")
         # No project registered in the DB
         self.assertEqual(output.status_code, 404)
@@ -3066,8 +3472,8 @@ class PagureFlaskRepotests(tests.Modeltests):
         )
 
     def test_view_commit_with_full_link(self):
-        """ Test the view_commit endpoint when the commit message includes
-        an url. """
+        """Test the view_commit endpoint when the commit message includes
+        an url."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -3111,8 +3517,8 @@ class PagureFlaskRepotests(tests.Modeltests):
         )
 
     def test_view_commit_with_short_link(self):
-        """ Test the view_commit endpoint when the commit message includes
-        an url. """
+        """Test the view_commit endpoint when the commit message includes
+        an url."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -3153,7 +3559,7 @@ class PagureFlaskRepotests(tests.Modeltests):
         )
 
     def test_view_commit_patch(self):
-        """ Test the view_commit_patch endpoint. """
+        """Test the view_commit_patch endpoint."""
 
         # No project registered in the DB
         output = self.app.get("/foo/c/bar.patch")
@@ -3295,7 +3701,7 @@ index 0000000..fb7093d
         )
 
     def test_view_commit_diff(self):
-        """ Test the view_commit_diff endpoint. """
+        """Test the view_commit_diff endpoint."""
 
         # No project registered in the DB
         output = self.app.get("/foo/c/bar.diff")
@@ -3349,7 +3755,7 @@ index 0000000..fb7093d
         )
 
     def test_view_tree(self):
-        """ Test the view_tree endpoint. """
+        """Test the view_tree endpoint."""
         output = self.app.get("/foo/tree/")
         # No project registered in the DB
         self.assertEqual(output.status_code, 404)
@@ -3469,8 +3875,8 @@ index 0000000..fb7093d
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.decorators.admin_session_timedout")
     def test_delete_repo_when_turned_off(self, ast, send_email):
-        """ Test the delete_repo endpoint when deletion of a repo is
-        turned off in the pagure instance """
+        """Test the delete_repo endpoint when deletion of a repo is
+        turned off in the pagure instance"""
         ast.return_value = False
         send_email.return_value = True
 
@@ -3691,7 +4097,7 @@ index 0000000..fb7093d
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.decorators.admin_session_timedout")
     def test_delete_read_only_repo(self, ast, send_email):
-        """ Test the delete_repo endpoint when the repo is read_only """
+        """Test the delete_repo endpoint when the repo is read_only"""
         ast.return_value = False
         send_email.return_value = True
 
@@ -3740,7 +4146,7 @@ index 0000000..fb7093d
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     @patch("pagure.decorators.admin_session_timedout")
     def test_delete_repo(self, ast):
-        """ Test the delete_repo endpoint. """
+        """Test the delete_repo endpoint."""
         ast.return_value = False
 
         # No Git repo
@@ -4158,8 +4564,8 @@ index 0000000..fb7093d
         MagicMock(return_value=False),
     )
     def test_delete_repo_no_ticket(self):
-        """ Test the delete_repo endpoint when tickets aren't enabled in
-        this pagure instance. """
+        """Test the delete_repo endpoint when tickets aren't enabled in
+        this pagure instance."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
@@ -4227,7 +4633,7 @@ index 0000000..fb7093d
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.decorators.admin_session_timedout")
     def test_delete_repo_with_users(self, ast, send_email):
-        """ Test the delete_repo endpoint. """
+        """Test the delete_repo endpoint."""
         ast.return_value = False
         send_email.return_value = True
 
@@ -4357,7 +4763,7 @@ index 0000000..fb7093d
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.decorators.admin_session_timedout")
     def test_delete_repo_with_group(self, ast, send_email):
-        """ Test the delete_repo endpoint. """
+        """Test the delete_repo endpoint."""
         ast.return_value = False
         send_email.return_value = True
 
@@ -4499,7 +4905,7 @@ index 0000000..fb7093d
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.decorators.admin_session_timedout")
     def test_delete_repo_with_coloredtag(self, ast, send_email):
-        """ Test the delete_repo endpoint. """
+        """Test the delete_repo endpoint."""
         ast.return_value = False
         send_email.return_value = True
 
@@ -4633,7 +5039,7 @@ index 0000000..fb7093d
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_new_repo_hook_token(self, ast):
-        """ Test the new_repo_hook_token endpoint. """
+        """Test the new_repo_hook_token endpoint."""
         ast.return_value = False
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
@@ -4695,7 +5101,7 @@ index 0000000..fb7093d
         self.assertNotEqual(repo.hook_token, "aaabbbccc")
 
     def test_view_tags(self):
-        """ Test the view_tags endpoint. """
+        """Test the view_tags endpoint."""
         output = self.app.get("/foo/releases")
         # No project registered in the DB
         self.assertEqual(output.status_code, 404)
@@ -4739,7 +5145,7 @@ index 0000000..fb7093d
         )
 
     def test_edit_file_no_signed_off(self):
-        """ Test the edit_file endpoint when signed-off isn't enforced. """
+        """Test the edit_file endpoint when signed-off isn't enforced."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -4774,7 +5180,7 @@ index 0000000..fb7093d
             )
 
     def test_edit_file_signed_off(self):
-        """ Test the edit_file endpoint when signed-off is enforced. """
+        """Test the edit_file endpoint when signed-off is enforced."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -4816,7 +5222,7 @@ index 0000000..fb7093d
             )
 
     def test_edit_file(self):
-        """ Test the edit_file endpoint. """
+        """Test the edit_file endpoint."""
 
         # No Git repo
         output = self.app.get("/foo/edit/foo/f/sources")
@@ -5032,7 +5438,7 @@ index 0000000..fb7093d
             self.assertIn("<p>No content found</p>", output_text)
 
     def test_edit_file_default_email(self):
-        """ Test the default email shown by the edit_file endpoint. """
+        """Test the default email shown by the edit_file endpoint."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -5076,7 +5482,7 @@ index 0000000..fb7093d
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_change_ref_head(self, ast):
-        """ Test the change_ref_head endpoint. """
+        """Test the change_ref_head endpoint."""
         ast.return_value = True
 
         # No Git repo
@@ -5220,7 +5626,7 @@ index 0000000..fb7093d
             self.assertIn("Default branch updated " "to master", output_text)
 
     def test_new_release(self):
-        """ Test the new_release endpoint. """
+        """Test the new_release endpoint."""
 
         # No Git repo
         output = self.app.post("/foo/upload/")
@@ -5317,7 +5723,7 @@ index 0000000..fb7093d
             self.assertIn("This project has not been tagged.", output_text)
 
     def test_new_release_two_files(self):
-        """ Test the new_release endpoint when uploading two files. """
+        """Test the new_release endpoint when uploading two files."""
         tests.create_projects(self.session)
         repo = tests.create_projects_git(os.path.join(self.path, "repos"))
 
@@ -5399,7 +5805,7 @@ index 0000000..fb7093d
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_add_token_all_tokens(self, ast):
-        """ Test the add_token endpoint. """
+        """Test the add_token endpoint."""
         ast.return_value = False
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -5418,7 +5824,7 @@ index 0000000..fb7093d
     @patch.dict("pagure.config.config", {"USER_ACLS": ["create_project"]})
     @patch("pagure.decorators.admin_session_timedout")
     def test_add_token_one_token(self, ast):
-        """ Test the add_token endpoint. """
+        """Test the add_token endpoint."""
         ast.return_value = False
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -5435,7 +5841,7 @@ index 0000000..fb7093d
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_add_token(self, ast):
-        """ Test the add_token endpoint. """
+        """Test the add_token endpoint."""
         ast.return_value = False
 
         # No Git repo
@@ -5517,7 +5923,7 @@ index 0000000..fb7093d
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_revoke_api_token(self, ast):
-        """ Test the revoke_api_token endpoint. """
+        """Test the revoke_api_token endpoint."""
         ast.return_value = False
 
         # No Git repo
@@ -5616,7 +6022,7 @@ index 0000000..fb7093d
 
     @patch("pagure.decorators.admin_session_timedout")
     def test_renew_api_token(self, ast):
-        """ Test the renew_api_token endpoint. """
+        """Test the renew_api_token endpoint."""
         ast.return_value = False
 
         # No Git repo
@@ -5720,7 +6126,7 @@ index 0000000..fb7093d
             )
 
     def test_delete_branch(self):
-        """ Test the delete_branch endpoint. """
+        """Test the delete_branch endpoint."""
         # No Git repo
         output = self.app.post("/foo/b/master/delete")
         self.assertEqual(output.status_code, 404)
@@ -5747,7 +6153,7 @@ index 0000000..fb7093d
             self.assertEqual(output.status_code, 403)
             output_text = output.get_data(as_text=True)
             self.assertIn(
-                "<p>You are not allowed to delete the master branch</p>",
+                "<p>You are not allowed to delete the default branch: master</p>",
                 output_text,
             )
 
@@ -5807,7 +6213,7 @@ index 0000000..fb7093d
             )
 
     def test_delete_branch_unicode(self):
-        """ Test the delete_branch endpoint with an unicode branch. """
+        """Test the delete_branch endpoint with an unicode branch."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -5841,10 +6247,53 @@ index 0000000..fb7093d
             output_text = output.get_data(as_text=True)
             self.assertNotIn('<form id="delete_branch_form-☃️"', output_text)
 
+    def test_delete_branch_master(self):
+        """Test the delete_branch endpoint with the master branch."""
+
+        tests.create_projects(self.session)
+        tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
+        project = pagure.lib.query._get_project(self.session, "test")
+
+        user = tests.FakeUser(username="pingou")
+        with tests.user_set(self.app.application, user):
+
+            # Add a branch that we can delete
+            path = os.path.join(self.path, "repos", "test.git")
+            tests.add_content_git_repo(path)
+            repo = pygit2.Repository(path)
+            branchname = "main"
+            repo.create_branch(branchname, repo.head.peel())
+
+            # Make that branch be the default one:
+            pagure.lib.git.git_set_ref_head(project=project, branch=branchname)
+
+            # Check before deletion
+            output = self.app.get("/test")
+            self.assertEqual(output.status_code, 200)
+
+            output = self.app.get("/test/branches")
+            output_text = output.get_data(as_text=True)
+            self.assertIn('<form id="delete_branch_form-master"', output_text)
+            self.assertNotIn('<form id="delete_branch_form-main"', output_text)
+
+            # Delete the branch
+            output = self.app.post(
+                "/test/b/master/delete", follow_redirects=True
+            )
+            self.assertEqual(output.status_code, 200)
+
+            # Check after deletion
+            output = self.app.get("/test/branches")
+            output_text = output.get_data(as_text=True)
+            self.assertNotIn(
+                '<form id="delete_branch_form-master"', output_text
+            )
+            self.assertNotIn('<form id="delete_branch_form-main"', output_text)
+
     @patch.dict("pagure.config.config", {"ALLOW_DELETE_BRANCH": False})
     def test_delete_branch_disabled_in_ui(self):
-        """ Test that the delete branch button doesn't show when the feature
-        is turned off. """
+        """Test that the delete branch button doesn't show when the feature
+        is turned off."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -5867,8 +6316,8 @@ index 0000000..fb7093d
 
     @patch.dict("pagure.config.config", {"ALLOW_DELETE_BRANCH": False})
     def test_delete_branch_disabled(self):
-        """ Test the delete_branch endpoint when it's disabled in the entire
-        instance. """
+        """Test the delete_branch endpoint when it's disabled in the entire
+        instance."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -5897,8 +6346,8 @@ index 0000000..fb7093d
 
     @patch.dict("pagure.config.config", {"ALLOW_DELETE_BRANCH": False})
     def test_delete_branch_disabled_fork(self):
-        """ Test the delete_branch endpoint when it's disabled in the entire
-        instance. """
+        """Test the delete_branch endpoint when it's disabled in the entire
+        instance."""
         item = pagure.lib.model.Project(
             user_id=2,  # foo
             name="test",
@@ -5942,7 +6391,7 @@ index 0000000..fb7093d
             )
 
     def test_view_docs(self):
-        """ Test the view_docs endpoint. """
+        """Test the view_docs endpoint."""
         output = self.app.get("/docs/foo/")
         # No project registered in the DB
         self.assertEqual(output.status_code, 404)
@@ -5959,7 +6408,7 @@ index 0000000..fb7093d
         self.assertEqual(output.status_code, 404)
 
     def test_view_project_activity(self):
-        """ Test the view_project_activity endpoint. """
+        """Test the view_project_activity endpoint."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -5980,7 +6429,7 @@ index 0000000..fb7093d
         self.assertEqual(output.status_code, 404)
 
     def test_goimport(self):
-        """ Test the go-import tag. """
+        """Test the go-import tag."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
         output = self.app.get("/test/")
@@ -5994,7 +6443,7 @@ index 0000000..fb7093d
         )
 
     def test_watch_repo(self):
-        """ Test the  watch_repo endpoint. """
+        """Test the  watch_repo endpoint."""
 
         output = self.app.post("/watch/")
         self.assertEqual(output.status_code, 405)
@@ -6206,7 +6655,7 @@ index 0000000..fb7093d
             )
 
     def test_delete_report(self):
-        """ Test the  delete_report endpoint. """
+        """Test the  delete_report endpoint."""
 
         output = self.app.post("/test/delete/report")
         self.assertEqual(output.status_code, 404)
@@ -6296,7 +6745,7 @@ index 0000000..fb7093d
             self.assertEqual(project.reports, {})
 
     def test_delete_report_ns_project(self):
-        """ Test the  delete_report endpoint on a namespaced project. """
+        """Test the  delete_report endpoint on a namespaced project."""
 
         output = self.app.post("/foo/test/delete/report")
         self.assertEqual(output.status_code, 404)
@@ -6400,7 +6849,7 @@ index 0000000..fb7093d
             self.assertEqual(project.reports, {})
 
     def test_open_pr_button_empty_repo(self):
-        """ Test "Open Pull-Request" button on empty project. """
+        """Test "Open Pull-Request" button on empty project."""
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -6418,7 +6867,7 @@ index 0000000..fb7093d
         {"UPLOAD_FOLDER_PATH": None, "UPLOAD_FOLDER_URL": None},
     )
     def test_releases_upload_folder_vars_None(self):
-        """ Test that /releases/ page of a repo displays correctly with
+        """Test that /releases/ page of a repo displays correctly with
         UPLOAD_FOLDER_PATH and UPLOAD_FOLDER_URL set to None
         """
         tests.create_projects(self.session)
@@ -6432,10 +6881,10 @@ index 0000000..fb7093d
 
 
 class PagureFlaskRepoTestHooktests(tests.Modeltests):
-    """ Tests for the web hook test function """
+    """Tests for the web hook test function"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskRepoTestHooktests, self).setUp()
 
         tests.create_projects(self.session)
@@ -6446,7 +6895,7 @@ class PagureFlaskRepoTestHooktests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_test_hook_no_project(self):
-        """ Test the test_hook endpoint when the project doesn't exist. """
+        """Test the test_hook endpoint when the project doesn't exist."""
         # No project
         output = self.app.post("/foo/settings/test_hook")
         self.assertEqual(output.status_code, 404)
@@ -6456,7 +6905,7 @@ class PagureFlaskRepoTestHooktests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_test_hook_existing_project(self):
-        """ Test the test_hook endpoint when the project doesn't exist. """
+        """Test the test_hook endpoint when the project doesn't exist."""
         user = tests.FakeUser()
         with tests.user_set(self.app.application, user):
             output = self.app.post("/test/settings/test_hook")
@@ -6467,7 +6916,7 @@ class PagureFlaskRepoTestHooktests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_test_hook_logged_out(self):
-        """ Test the test_hook endpoint when the project isn't logged in. """
+        """Test the test_hook endpoint when the project isn't logged in."""
         # User not logged in
         output = self.app.post("/test/settings/test_hook")
         self.assertEqual(output.status_code, 302)
@@ -6477,7 +6926,7 @@ class PagureFlaskRepoTestHooktests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_test_hook_logged_in_no_csrf(self):
-        """ Test the test_hook endpoint when the user is logged in. """
+        """Test the test_hook endpoint when the user is logged in."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/test/settings/test_hook")
@@ -6489,7 +6938,7 @@ class PagureFlaskRepoTestHooktests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_test_hook_logged_in_csrf(self):
-        """ Test the test_hook endpoint when the user is logged in. """
+        """Test the test_hook endpoint when the user is logged in."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             data = {"csrf_token": self.get_csrf()}
@@ -6498,7 +6947,7 @@ class PagureFlaskRepoTestHooktests(tests.Modeltests):
 
 
 class PagureFlaskRepoTestRegenerateGittests(tests.Modeltests):
-    """ Tests for the regenerate git repo function """
+    """Tests for the regenerate git repo function"""
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     @patch(
@@ -6506,7 +6955,7 @@ class PagureFlaskRepoTestRegenerateGittests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskRepoTestRegenerateGittests, self).setUp()
 
         tests.create_projects(self.session)
@@ -6517,14 +6966,14 @@ class PagureFlaskRepoTestRegenerateGittests(tests.Modeltests):
             self.csrf_token = self.get_csrf()
 
     def test_regenerate_git_invalid_project(self):
-        """ Test the regenerate_git endpoint. """
+        """Test the regenerate_git endpoint."""
         user = tests.FakeUser()
         with tests.user_set(self.app.application, user):
             output = self.app.post("/foo/regenerate")
             self.assertEqual(output.status_code, 404)
 
     def test_regenerate_git_invalid_user(self):
-        """ Test the regenerate_git endpoint. """
+        """Test the regenerate_git endpoint."""
         user = tests.FakeUser()
         with tests.user_set(self.app.application, user):
             output = self.app.post("/test/regenerate")
@@ -6535,21 +6984,21 @@ class PagureFlaskRepoTestRegenerateGittests(tests.Modeltests):
         MagicMock(return_value=True),
     )
     def test_regenerate_git_user_session_timeout(self):
-        """ Test the regenerate_git endpoint. """
+        """Test the regenerate_git endpoint."""
         user = tests.FakeUser()
         with tests.user_set(self.app.application, user):
             output = self.app.post("/test/regenerate")
             self.assertEqual(output.status_code, 302)
 
     def test_regenerate_git_no_csrf(self):
-        """ Test the regenerate_git endpoint. """
+        """Test the regenerate_git endpoint."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.post("/test/regenerate")
             self.assertEqual(output.status_code, 400)
 
     def test_regenerate_git_missing_repo_type(self):
-        """ Test the regenerate_git endpoint. """
+        """Test the regenerate_git endpoint."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             data = {"csrf_token": self.csrf_token}
@@ -6558,7 +7007,7 @@ class PagureFlaskRepoTestRegenerateGittests(tests.Modeltests):
             self.assertEqual(output.status_code, 400)
 
     def test_regenerate_git_missing_invalid_regenerate(self):
-        """ Test the regenerate_git endpoint. """
+        """Test the regenerate_git endpoint."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             data = {"csrf_token": self.csrf_token, "regenerate": "ticket"}
@@ -6567,7 +7016,7 @@ class PagureFlaskRepoTestRegenerateGittests(tests.Modeltests):
 
     @patch("pagure.lib.git._update_git")
     def test_regenerate_git_tickets(self, upgit):
-        """ Test the regenerate_git endpoint. """
+        """Test the regenerate_git endpoint."""
         upgit.return_value = True
 
         user = tests.FakeUser(username="pingou")
@@ -6597,7 +7046,7 @@ class PagureFlaskRepoTestRegenerateGittests(tests.Modeltests):
 
     @patch("pagure.lib.git._update_git")
     def test_regenerate_git_requests(self, upgit):
-        """ Test the regenerate_git endpoint. """
+        """Test the regenerate_git endpoint."""
         # upgit.return_value = True
 
         user = tests.FakeUser(username="pingou")
@@ -6629,10 +7078,10 @@ class PagureFlaskRepoTestRegenerateGittests(tests.Modeltests):
 
 
 class PagureFlaskRepoTestGitSSHURL(tests.Modeltests):
-    """ Tests the display of the SSH url in the UI """
+    """Tests the display of the SSH url in the UI"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskRepoTestGitSSHURL, self).setUp()
 
         tests.create_projects(self.session)
@@ -6672,7 +7121,7 @@ class PagureFlaskRepoTestGitSSHURL(tests.Modeltests):
         self.assertEqual(msg, "SSH key added")
 
     def test_logged_out(self):
-        """ Test the default behavior with the user logged out. """
+        """Test the default behavior with the user logged out."""
 
         output = self.app.get("/test")
         self.assertEqual(output.status_code, 200)
@@ -6690,7 +7139,7 @@ class PagureFlaskRepoTestGitSSHURL(tests.Modeltests):
         )
 
     def test_logged_in(self):
-        """ Test the default behavior with the user logged in. """
+        """Test the default behavior with the user logged in."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.get("/test")
@@ -6710,7 +7159,7 @@ class PagureFlaskRepoTestGitSSHURL(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"SSH_ACCESS_GROUPS": ["packager"]})
     def test_ssh_restricted_user_member(self):
-        """ Test when ssh is restricted and the user has access. """
+        """Test when ssh is restricted and the user has access."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.get("/test")
@@ -6730,7 +7179,7 @@ class PagureFlaskRepoTestGitSSHURL(tests.Modeltests):
 
     @patch.dict("pagure.config.config", {"SSH_ACCESS_GROUPS": ["invalid"]})
     def test_ssh_restricted_user_non_member(self):
-        """ Test when ssh is restricted and the user does not have access. """
+        """Test when ssh is restricted and the user does not have access."""
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
             output = self.app.get("/test")
diff --git a/tests/test_pagure_flask_ui_repo_delete_project.py b/tests/test_pagure_flask_ui_repo_delete_project.py
index 0258335..94e8762 100644
--- a/tests/test_pagure_flask_ui_repo_delete_project.py
+++ b/tests/test_pagure_flask_ui_repo_delete_project.py
@@ -13,7 +13,9 @@ from __future__ import unicode_literals, absolute_import
 import sys
 import os
 
-from mock import patch, MagicMock
+import pagure_messages
+from mock import ANY, patch, MagicMock
+from fedora_messaging import testing
 
 sys.path.insert(
     0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
@@ -24,10 +26,10 @@ import tests
 
 
 class PagureFlaskDeleteRepotests(tests.Modeltests):
-    """ Tests for deleting a project in pagure """
+    """Tests for deleting a project in pagure"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskDeleteRepotests, self).setUp()
 
         # Create some projects
@@ -71,7 +73,7 @@ class PagureFlaskDeleteRepotests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_delete_repo_when_turned_off(self):
-        """ Test the delete_repo endpoint for a fork when only deleting main
+        """Test the delete_repo endpoint for a fork when only deleting main
         project is forbidden.
         """
 
@@ -89,7 +91,7 @@ class PagureFlaskDeleteRepotests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_delete_button_present(self):
-        """ Test that the delete button is present when deletions are
+        """Test that the delete button is present when deletions are
         allowed.
         """
 
@@ -114,7 +116,7 @@ class PagureFlaskDeleteRepotests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_delete_button_absent(self):
-        """ Test that the delete button is absent when deletions are not
+        """Test that the delete button is absent when deletions are not
         allowed.
         """
 
@@ -140,7 +142,7 @@ class PagureFlaskDeleteRepotests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_delete_fork_when_project_off_refreshing(self):
-        """ Test the delete_repo endpoint for a fork when only deleting main
+        """Test the delete_repo endpoint for a fork when only deleting main
         project is forbidden but the fork is being refreshed in the backend
         """
         project = pagure.lib.query.get_authorized_project(
@@ -169,6 +171,9 @@ class PagureFlaskDeleteRepotests(tests.Modeltests):
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 4)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     @patch.dict("pagure.config.config", {"ENABLE_DEL_PROJECTS": False})
     @patch.dict("pagure.config.config", {"ENABLE_DEL_FORKS": True})
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
@@ -177,7 +182,7 @@ class PagureFlaskDeleteRepotests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_delete_fork_when_project_off(self):
-        """ Test the delete_repo endpoint for a fork when only deleting main
+        """Test the delete_repo endpoint for a fork when only deleting main
         project is forbidden.
         """
         project = pagure.lib.query.get_authorized_project(
@@ -191,10 +196,94 @@ class PagureFlaskDeleteRepotests(tests.Modeltests):
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
-            output = self.app.post(
-                "/fork/pingou/test/delete", follow_redirects=True
-            )
-            self.assertEqual(output.status_code, 200)
+            with testing.mock_sends(
+                pagure_messages.ProjectDeletedV1(
+                    topic="pagure.project.deleted",
+                    body={
+                        "project": {
+                            "id": 4,
+                            "name": "test",
+                            "fullname": "forks/pingou/test",
+                            "url_path": "fork/pingou/test",
+                            "full_url": "http://localhost.localdomain/fork/pingou/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [],
+                            "milestones": {},
+                        },
+                        "agent": "pingou",
+                    },
+                )
+            ):
+                output = self.app.post(
+                    "/fork/pingou/test/delete", follow_redirects=True
+                )
+                self.assertEqual(output.status_code, 200)
 
         projects = pagure.lib.query.search_projects(self.session)
         self.assertEqual(len(projects), 3)
@@ -207,7 +296,7 @@ class PagureFlaskDeleteRepotests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_delete_fork_when_fork_and_project_off(self):
-        """ Test the delete_repo endpoint for a fork when deleting fork and
+        """Test the delete_repo endpoint for a fork when deleting fork and
         project is forbidden.
         """
 
@@ -229,7 +318,7 @@ class PagureFlaskDeleteRepotests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_delete_fork_button_absent(self):
-        """ Test that the delete button is absent when deletions are not
+        """Test that the delete button is absent when deletions are not
         allowed.
         """
 
@@ -257,7 +346,7 @@ class PagureFlaskDeleteRepotests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_delete_fork_button_fork_del_allowed(self):
-        """ Test that the delete button is present when deletions of projects
+        """Test that the delete button is present when deletions of projects
         is not allowed but deletions of forks is.
         """
 
@@ -294,7 +383,7 @@ class PagureFlaskDeleteRepotests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_delete_fork_button_fork_del_allowed_read_only(self):
-        """ Test that the delete button is absent when deletions of projects
+        """Test that the delete button is absent when deletions of projects
         is not allowed but deletions of forks is but fork is still being
         processed.
         """
diff --git a/tests/test_pagure_flask_ui_repo_flag_commit.py b/tests/test_pagure_flask_ui_repo_flag_commit.py
index 4da2590..ff416a5 100644
--- a/tests/test_pagure_flask_ui_repo_flag_commit.py
+++ b/tests/test_pagure_flask_ui_repo_flag_commit.py
@@ -26,10 +26,10 @@ import tests
 
 
 class ViewCommitFlagtests(tests.SimplePagureTest):
-    """ Tests for the UI related to commit flags """
+    """Tests for the UI related to commit flags"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(ViewCommitFlagtests, self).setUp()
 
         tests.create_projects(self.session)
@@ -41,7 +41,7 @@ class ViewCommitFlagtests(tests.SimplePagureTest):
         self.commit = repo.revparse_single("HEAD")
 
     def test_view_commit_no_flag(self):
-        """ Test the view_commit endpoint. """
+        """Test the view_commit endpoint."""
 
         # View first commit
         output = self.app.get("/test/c/%s" % self.commit.oid.hex)
@@ -59,7 +59,7 @@ class ViewCommitFlagtests(tests.SimplePagureTest):
         )
 
     def test_view_commit_pending_flag(self):
-        """ Test the view_commit endpoint with a pending flag. """
+        """Test the view_commit endpoint with a pending flag."""
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
 
         msg = pagure.lib.query.add_commit_flag(
@@ -104,7 +104,7 @@ class ViewCommitFlagtests(tests.SimplePagureTest):
         self.assertIn("<span>Build is running</span>", output_text)
 
     def test_view_commit_success_flag(self):
-        """ Test the view_commit endpoint with a successful flag. """
+        """Test the view_commit endpoint with a successful flag."""
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
 
         msg = pagure.lib.query.add_commit_flag(
@@ -149,7 +149,7 @@ class ViewCommitFlagtests(tests.SimplePagureTest):
         self.assertIn("<span>Build passed</span>", output_text)
 
     def test_view_commit_error_flag(self):
-        """ Test the view_commit endpoint with a error flag. """
+        """Test the view_commit endpoint with a error flag."""
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
 
         msg = pagure.lib.query.add_commit_flag(
@@ -194,7 +194,7 @@ class ViewCommitFlagtests(tests.SimplePagureTest):
         self.assertIn("<span>Build errored</span>", output_text)
 
     def test_view_commit_failure_flag(self):
-        """ Test the view_commit endpoint with a failure flag. """
+        """Test the view_commit endpoint with a failure flag."""
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
 
         msg = pagure.lib.query.add_commit_flag(
@@ -238,7 +238,7 @@ class ViewCommitFlagtests(tests.SimplePagureTest):
         self.assertIn("<span>Build failed</span>", output_text)
 
     def test_view_commit_canceled_flag(self):
-        """ Test the view_commit endpoint with a canceled flag. """
+        """Test the view_commit endpoint with a canceled flag."""
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
 
         msg = pagure.lib.query.add_commit_flag(
@@ -291,7 +291,7 @@ class ViewCommitFlagtests(tests.SimplePagureTest):
         },
     )
     def test_view_commit_with_custom_flags(self):
-        """ Test the view_commit endpoint while having custom flags. """
+        """Test the view_commit endpoint while having custom flags."""
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
 
         msg = pagure.lib.query.add_commit_flag(
diff --git a/tests/test_pagure_flask_ui_repo_milestones.py b/tests/test_pagure_flask_ui_repo_milestones.py
index ad72681..75bd756 100644
--- a/tests/test_pagure_flask_ui_repo_milestones.py
+++ b/tests/test_pagure_flask_ui_repo_milestones.py
@@ -25,11 +25,11 @@ import tests
 
 
 class PagureFlaskRepoMilestonestests(tests.Modeltests):
-    """ Tests for milestones in pagure """
+    """Tests for milestones in pagure"""
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskRepoMilestonestests, self).setUp()
 
         tests.create_projects(self.session)
@@ -52,7 +52,7 @@ class PagureFlaskRepoMilestonestests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_milestones_settings_empty(self):
-        """ Test the settings page when no milestones are set. """
+        """Test the settings page when no milestones are set."""
 
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         self.assertEqual(repo.milestones, {})
@@ -96,7 +96,7 @@ class PagureFlaskRepoMilestonestests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_setting_retrieving_milestones(self):
-        """ Test setting and retrieving milestones off a project. """
+        """Test setting and retrieving milestones off a project."""
 
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
 
@@ -129,8 +129,7 @@ class PagureFlaskRepoMilestonestests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_issue_page_milestone_actives(self):
-        """ Test viewing tickets on a project having milestones, all active.
-        """
+        """Test viewing tickets on a project having milestones, all active."""
 
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
 
@@ -160,7 +159,7 @@ class PagureFlaskRepoMilestonestests(tests.Modeltests):
         MagicMock(return_value=False),
     )
     def test_issue_page_milestone_not_allactives(self):
-        """ Test viewing tickets on a project having milestones, not all
+        """Test viewing tickets on a project having milestones, not all
         being active.
         """
 
diff --git a/tests/test_pagure_flask_ui_repo_mirrored_from.py b/tests/test_pagure_flask_ui_repo_mirrored_from.py
index 6cbfc30..e5861ae 100644
--- a/tests/test_pagure_flask_ui_repo_mirrored_from.py
+++ b/tests/test_pagure_flask_ui_repo_mirrored_from.py
@@ -33,13 +33,12 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureUiRepoMirroredFromTests(tests.Modeltests):
-    """ Tests for pagure project that are mirrored from a remote location
-    """
+    """Tests for pagure project that are mirrored from a remote location"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureUiRepoMirroredFromTests, self).setUp()
 
         tests.create_projects(self.session)
@@ -54,7 +53,7 @@ class PagureUiRepoMirroredFromTests(tests.Modeltests):
         self.session.commit()
 
     def test_custom_projecticon(self):
-        """ Ensure that the customized project icon is shown the main page of
+        """Ensure that the customized project icon is shown the main page of
         the project.
         """
         output = self.app.get("/test")
@@ -66,7 +65,7 @@ class PagureUiRepoMirroredFromTests(tests.Modeltests):
         )
 
     def test_regular_projecticon(self):
-        """ Ensure that the customized project icon is shown the main page of
+        """Ensure that the customized project icon is shown the main page of
         the project.
         """
         output = self.app.get("/test2")
@@ -77,7 +76,7 @@ class PagureUiRepoMirroredFromTests(tests.Modeltests):
         )
 
     def test_settings_shows(self):
-        """ Ensure that the box to edit the mirrored from value shows up
+        """Ensure that the box to edit the mirrored from value shows up
         in the settings.
         """
         user = tests.FakeUser(username="pingou")
@@ -96,7 +95,7 @@ class PagureUiRepoMirroredFromTests(tests.Modeltests):
             )
 
     def test_settings_not_show(self):
-        """ Ensure that the box to edit the mirrored from value does not
+        """Ensure that the box to edit the mirrored from value does not
         show up in the settings when it shouldn't.
         """
         user = tests.FakeUser(username="pingou")
@@ -114,7 +113,7 @@ class PagureUiRepoMirroredFromTests(tests.Modeltests):
             )
 
     def test_edit_mirrored_from(self):
-        """ Ensure that we can successfully edit the content of the
+        """Ensure that we can successfully edit the content of the
         mirrored_from field.
         """
         user = tests.FakeUser(username="pingou")
diff --git a/tests/test_pagure_flask_ui_repo_slash_name.py b/tests/test_pagure_flask_ui_repo_slash_name.py
index 028f542..5d505ff 100644
--- a/tests/test_pagure_flask_ui_repo_slash_name.py
+++ b/tests/test_pagure_flask_ui_repo_slash_name.py
@@ -30,15 +30,14 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureFlaskSlashInNametests(tests.SimplePagureTest):
-    """ Tests for flask application when the project contains a '/'.
-    """
+    """Tests for flask application when the project contains a '/'."""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskSlashInNametests, self).setUp()
 
     def set_up_git_repo(self, name="test"):
-        """ Set up the git repo to play with. """
+        """Set up the git repo to play with."""
 
         # Create a git repo to play with
         gitrepo = os.path.join(self.path, "repos", "%s.git" % name)
@@ -74,7 +73,7 @@ class PagureFlaskSlashInNametests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_view_repo_empty(self, send_email):
-        """ Test the view_repo endpoint when the project has a slash in its
+        """Test the view_repo endpoint when the project has a slash in its
         name.
         """
         send_email.return_value = True
@@ -159,7 +158,7 @@ class PagureFlaskSlashInNametests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_view_repo(self, send_email):
-        """ Test the view_repo endpoint when the project has a slash in its
+        """Test the view_repo endpoint when the project has a slash in its
         name.
         """
         send_email.return_value = True
diff --git a/tests/test_pagure_flask_ui_repo_view_blame.py b/tests/test_pagure_flask_ui_repo_view_blame.py
index bcf0ccc..78ebaf2 100644
--- a/tests/test_pagure_flask_ui_repo_view_blame.py
+++ b/tests/test_pagure_flask_ui_repo_view_blame.py
@@ -24,10 +24,10 @@ from pagure.utils import __get_file_in_tree as get_file_in_tree
 
 
 class PagureFlaskRepoViewBlameFileSimpletests(tests.Modeltests):
-    """ Tests for view_blame_file endpoint of the flask pagure app """
+    """Tests for view_blame_file endpoint of the flask pagure app"""
 
     def test_view_blame_file_no_project(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         output = self.app.get("/foo/blame/sources")
         # No project registered in the DB
         self.assertEqual(output.status_code, 404)
@@ -39,7 +39,7 @@ class PagureFlaskRepoViewBlameFileSimpletests(tests.Modeltests):
         self.assertIn("<p>Project not found</p>", output_text)
 
     def test_view_blame_file_no_git_repo(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         tests.create_projects(self.session)
 
         output = self.app.get("/test/blame/sources")
@@ -47,7 +47,7 @@ class PagureFlaskRepoViewBlameFileSimpletests(tests.Modeltests):
         self.assertEqual(output.status_code, 404)
 
     def test_view_blame_file_no_git_content(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -63,10 +63,10 @@ class PagureFlaskRepoViewBlameFileSimpletests(tests.Modeltests):
 
 
 class PagureFlaskRepoViewBlameFiletests(tests.Modeltests):
-    """ Tests for view_blame_file endpoint of the flask pagure app """
+    """Tests for view_blame_file endpoint of the flask pagure app"""
 
     def setUp(self):
-        """ Set up the environment, ran before every tests. """
+        """Set up the environment, ran before every tests."""
         super(PagureFlaskRepoViewBlameFiletests, self).setUp()
         self.regex = re.compile(r'>(\w+)</a></td>\n<td class="cell2">')
         tests.create_projects(self.session)
@@ -89,7 +89,7 @@ class PagureFlaskRepoViewBlameFiletests(tests.Modeltests):
         )
 
     def test_view_blame_file_default_branch_master(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         output = self.app.get("/test/blame/sources")
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
@@ -108,7 +108,7 @@ class PagureFlaskRepoViewBlameFiletests(tests.Modeltests):
         self.assertEqual(len(data), 2)
 
     def test_view_blame_file_default_branch_non_master(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         repo = pygit2.Repository(os.path.join(self.path, "repos", "test.git"))
         reference = repo.lookup_reference("refs/heads/feature").resolve()
         repo.set_head(reference.name)
@@ -130,7 +130,7 @@ class PagureFlaskRepoViewBlameFiletests(tests.Modeltests):
         self.assertEqual(len(data), 3)
 
     def test_view_blame_file_on_commit(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         repo_obj = pygit2.Repository(
             os.path.join(self.path, "repos", "test.git")
         )
@@ -157,7 +157,7 @@ class PagureFlaskRepoViewBlameFiletests(tests.Modeltests):
         self.assertEqual(len(data), 1)
 
     def test_view_blame_file_on_branch(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         output = self.app.get("/test/blame/sources?identifier=feature")
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
@@ -176,7 +176,7 @@ class PagureFlaskRepoViewBlameFiletests(tests.Modeltests):
         self.assertEqual(len(data), 3)
 
     def test_view_blame_file_on_tag(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         # set a tag on the head's parent commit
         repo_obj = pygit2.Repository(
             os.path.join(self.path, "repos", "test.git")
@@ -206,7 +206,7 @@ class PagureFlaskRepoViewBlameFiletests(tests.Modeltests):
         self.assertEqual(len(data), 1)
 
     def test_view_blame_file_on_blob(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         # Retrieve the blob of the `sources` file in head
         repo_obj = pygit2.Repository(
             os.path.join(self.path, "repos", "test.git")
@@ -224,7 +224,7 @@ class PagureFlaskRepoViewBlameFiletests(tests.Modeltests):
         self.assertIn("Invalid identified provided", output_text)
 
     def test_view_blame_file_binary(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         # Add binary content
         tests.add_binary_git_repo(
             os.path.join(self.path, "repos", "test.git"), "test.jpg"
@@ -236,7 +236,7 @@ class PagureFlaskRepoViewBlameFiletests(tests.Modeltests):
         self.assertIn("<p>Binary files cannot be blamed</p>", output_text)
 
     def test_view_blame_file_non_ascii_name(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         tests.add_commit_git_repo(
             os.path.join(self.path, "repos", "test.git"),
             ncommits=1,
@@ -261,7 +261,7 @@ class PagureFlaskRepoViewBlameFiletests(tests.Modeltests):
         )
 
     def test_view_blame_file_fork_of_a_fork(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
             name="test3",
@@ -304,7 +304,7 @@ class PagureFlaskRepoViewBlameFiletests(tests.Modeltests):
         )
 
     def test_view_blame_file_no_file(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         output = self.app.get("/test/blame/foofile")
         self.assertEqual(output.status_code, 404)
         output_text = output.get_data(as_text=True)
@@ -315,7 +315,7 @@ class PagureFlaskRepoViewBlameFiletests(tests.Modeltests):
         self.assertIn("<p>File not found</p>", output_text)
 
     def test_view_blame_file_folder(self):
-        """ Test the view_blame_file endpoint """
+        """Test the view_blame_file endpoint"""
         tests.add_commit_git_repo(
             os.path.join(self.path, "repos", "test.git/folder1"),
             ncommits=1,
diff --git a/tests/test_pagure_flask_ui_repo_view_file.py b/tests/test_pagure_flask_ui_repo_view_file.py
index 16a1ce1..3e40c52 100644
--- a/tests/test_pagure_flask_ui_repo_view_file.py
+++ b/tests/test_pagure_flask_ui_repo_view_file.py
@@ -26,10 +26,10 @@ from pagure.lib.repo import PagureRepo  # noqa
 
 
 class LocalBasetests(tests.Modeltests):
-    """ Tests for view_file endpoint of the flask pagure app """
+    """Tests for view_file endpoint of the flask pagure app"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(LocalBasetests, self).setUp()
 
         pagure.config.config["VIRUS_SCAN_ATTACHMENTS"] = False
@@ -40,16 +40,16 @@ class LocalBasetests(tests.Modeltests):
 
 
 class PagureFlaskRepoViewFileSimpletests(LocalBasetests):
-    """ Tests for view_file endpoint of the flask pagure app """
+    """Tests for view_file endpoint of the flask pagure app"""
 
     def test_view_file_no_project(self):
-        """ Test the view_file when the project is unknown. """
+        """Test the view_file when the project is unknown."""
         output = self.app.get("/foo/blob/foo/f/sources")
         # No project registered in the DB
         self.assertEqual(output.status_code, 404)
 
     def test_view_file_no_git(self):
-        """ Test the view_file when the project has no git repo. """
+        """Test the view_file when the project has no git repo."""
         tests.create_projects(self.session)
 
         output = self.app.get("/test/blob/foo/f/sources")
@@ -57,7 +57,7 @@ class PagureFlaskRepoViewFileSimpletests(LocalBasetests):
         self.assertEqual(output.status_code, 404)
 
     def test_view_file_no_git_content(self):
-        """ Test the view_file when the file doesn't exist. """
+        """Test the view_file when the file doesn't exist."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -66,10 +66,10 @@ class PagureFlaskRepoViewFileSimpletests(LocalBasetests):
 
 
 class PagureFlaskRepoViewFiletests(LocalBasetests):
-    """ Tests for view_file endpoint of the flask pagure app """
+    """Tests for view_file endpoint of the flask pagure app"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskRepoViewFiletests, self).setUp()
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -87,7 +87,7 @@ class PagureFlaskRepoViewFiletests(LocalBasetests):
         )
 
     def test_view_file_invalid_file(self):
-        """ Test the view_file when the file doesn't exist. """
+        """Test the view_file when the file doesn't exist."""
 
         output = self.app.get("/test/blob/master/foofile")
         self.assertEqual(output.status_code, 404)
@@ -97,7 +97,7 @@ class PagureFlaskRepoViewFiletests(LocalBasetests):
         self.assertEqual(output.status_code, 404)
 
     def test_view_file_basic_text(self):
-        """ Test the view_file with a basic text file. """
+        """Test the view_file with a basic text file."""
         output = self.app.get("/test/blob/master/f/sources")
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
@@ -108,7 +108,7 @@ class PagureFlaskRepoViewFiletests(LocalBasetests):
         )
 
     def test_view_file_empty_file(self):
-        """ Test the view_file with an empty file. """
+        """Test the view_file with an empty file."""
 
         # Empty files should also be displayed
         tests.add_content_to_git(
@@ -130,20 +130,15 @@ class PagureFlaskRepoViewFiletests(LocalBasetests):
         )
 
     def test_view_file_binary_file(self):
-        """ Test the view_file with a binary file. """
+        """Test the view_file with a binary file."""
 
         # View what's supposed to be an image
         output = self.app.get("/test/blob/master/f/test.jpg")
         self.assertEqual(output.status_code, 200)
-        output_text = output.get_data(as_text=True)
-        self.assertIn("Binary files cannot be rendered.<br/>", output_text)
-        self.assertIn(
-            '<a href="/test/raw/master/f/test.jpg">view the raw version',
-            output_text,
-        )
+        self.assertNotIn(b"<html", output.data)
 
     def test_view_file_by_commit(self):
-        """ Test the view_file in a specific commit. """
+        """Test the view_file in a specific commit."""
 
         # View by commit id
         repo = pygit2.Repository(os.path.join(self.path, "repos", "test.git"))
@@ -151,32 +146,32 @@ class PagureFlaskRepoViewFiletests(LocalBasetests):
 
         output = self.app.get("/test/blob/%s/f/test.jpg" % commit.oid.hex)
         self.assertEqual(output.status_code, 200)
-        output_text = output.get_data(as_text=True)
-        self.assertIn("Binary files cannot be rendered.<br/>", output_text)
-        self.assertIn('/f/test.jpg">view the raw version', output_text)
+        self.assertNotIn(b"<html", output.data)
 
-    def test_view_file_by_name(self):
-        """ Test the view_file via a image name. """
-
-        # View by image name -- somehow we support this
+    def test_view_file_invalid_branch(self):
+        """Test the view_file via a image name."""
         output = self.app.get("/test/blob/sources/f/test.jpg")
-        self.assertEqual(output.status_code, 200)
-        output_text = output.get_data(as_text=True)
-        self.assertIn("Binary files cannot be rendered.<br/>", output_text)
-        self.assertIn('/f/test.jpg">view the raw version', output_text)
-
-    def test_view_file_binary_file2(self):
-        """ Test the view_file with a binary file (2). """
+        self.assertEqual(output.status_code, 404)
 
-        # View binary file
+    def test_view_file_invalid_branch2(self):
+        """Test the view_file with a binary file (2)."""
         output = self.app.get("/test/blob/sources/f/test_binary")
+        self.assertEqual(output.status_code, 404)
+
+    def test_view_file_invalid_branch(self):
+        """Test the view_file via a image name."""
+        output = self.app.get("/test/blob/master/f/test.jpg")
         self.assertEqual(output.status_code, 200)
-        output_text = output.get_data(as_text=True)
-        self.assertIn('/f/test_binary">view the raw version', output_text)
-        self.assertTrue("Binary files cannot be rendered.<br/>" in output_text)
+        self.assertNotIn(b"<html", output.data)
+
+    def test_view_file_invalid_branch2(self):
+        """Test the view_file with a binary file (2)."""
+        output = self.app.get("/test/blob/master/f/test_binary")
+        self.assertEqual(output.status_code, 200)
+        self.assertNotIn(b"<html", output.data)
 
     def test_view_file_for_folder(self):
-        """ Test the view_file with a folder. """
+        """Test the view_file with a folder."""
 
         # View folder
         output = self.app.get("/test/blob/master/f/folder1")
@@ -191,7 +186,7 @@ class PagureFlaskRepoViewFiletests(LocalBasetests):
         )
 
     def test_view_file_nested_file(self):
-        """ Test the view_file with a nested file. """
+        """Test the view_file with a nested file."""
 
         # Verify the nav links correctly when viewing a nested folder/file.
         output = self.app.get("/test/blob/master/f/folder1/folder2/file")
@@ -206,7 +201,7 @@ class PagureFlaskRepoViewFiletests(LocalBasetests):
         )
 
     def test_view_file_non_ascii_file(self):
-        """ Test the view_file with a non-ascii file name. """
+        """Test the view_file with a non-ascii file name."""
 
         # View file with a non-ascii name
         tests.add_commit_git_repo(
@@ -228,7 +223,7 @@ class PagureFlaskRepoViewFiletests(LocalBasetests):
         )
 
     def test_view_file_fork_and_edit_logged_out(self):
-        """ Test the view_file fork and edit button presence when logged
+        """Test the view_file fork and edit button presence when logged
         out.
         """
 
@@ -249,7 +244,7 @@ class PagureFlaskRepoViewFiletests(LocalBasetests):
         )
 
     def test_view_file_fork_and_edit_logged_in(self):
-        """ Test the view_file fork and edit button presence when logged
+        """Test the view_file fork and edit button presence when logged
         in.
         """
 
@@ -273,10 +268,10 @@ class PagureFlaskRepoViewFiletests(LocalBasetests):
 
 
 class PagureFlaskRepoViewFileForktests(LocalBasetests):
-    """ Tests for view_file endpoint of the flask pagure app for a fork """
+    """Tests for view_file endpoint of the flask pagure app for a fork"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskRepoViewFileForktests, self).setUp()
 
         tests.create_projects(self.session)
@@ -318,7 +313,7 @@ class PagureFlaskRepoViewFileForktests(LocalBasetests):
         )
 
     def test_view_file_nested_file_in_fork(self):
-        """ Test the view_file with a nested file in fork. """
+        """Test the view_file with a nested file in fork."""
         # Verify the nav links correctly when viewing a file/folder in a fork.
         output = self.app.get(
             "/fork/pingou/test/blob/master/f/folder1/folder2/file"
@@ -333,7 +328,7 @@ class PagureFlaskRepoViewFileForktests(LocalBasetests):
         )
 
     def test_view_file_in_branch_in_fork(self):
-        """ Test the view_file in a specific branch of a fork. """
+        """Test the view_file in a specific branch of a fork."""
         output = self.app.get("/fork/pingou/test/blob/master/f/sources")
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
@@ -346,7 +341,7 @@ class PagureFlaskRepoViewFileForktests(LocalBasetests):
         )
 
     def test_view_file_fork_and_edit_on_fork_logged_out(self):
-        """ Test the view_file on a text file on a fork when logged out. """
+        """Test the view_file on a text file on a fork when logged out."""
 
         # not logged in, no edit button but fork & edit is there
         output = self.app.get("/fork/pingou/test/blob/master/f/sources")
@@ -365,8 +360,7 @@ class PagureFlaskRepoViewFileForktests(LocalBasetests):
         )
 
     def test_view_file_fork_and_edit_on_your_fork(self):
-        """ Test the view_file on a text file on your fork when logged in.
-        """
+        """Test the view_file on a text file on your fork when logged in."""
 
         # logged in, but it's your own fork, so just edit button is there
         user = tests.FakeUser(username="pingou")
@@ -387,7 +381,7 @@ class PagureFlaskRepoViewFileForktests(LocalBasetests):
             )
 
     def test_view_file_fork_and_edit_on_a_fork(self):
-        """ Test the view_file on a text file on somone else's fork when
+        """Test the view_file on a text file on somone else's fork when
         logged in.
         """
 
@@ -411,7 +405,7 @@ class PagureFlaskRepoViewFileForktests(LocalBasetests):
             )
 
     def test_view_file_fork_and_edit_on_project(self):
-        """ Test the view_file on a text file on somone else's fork when
+        """Test the view_file on a text file on somone else's fork when
         logged in.
         """
 
diff --git a/tests/test_pagure_flask_ui_repo_view_history.py b/tests/test_pagure_flask_ui_repo_view_history.py
index 36e7f11..2536635 100644
--- a/tests/test_pagure_flask_ui_repo_view_history.py
+++ b/tests/test_pagure_flask_ui_repo_view_history.py
@@ -22,10 +22,10 @@ import pagure.lib.model
 
 
 class PagureFlaskRepoViewHistoryFileSimpletests(tests.Modeltests):
-    """ Tests for view_history_file endpoint of the flask pagure app """
+    """Tests for view_history_file endpoint of the flask pagure app"""
 
     def test_view_history_file_no_project(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         output = self.app.get("/foo/history/sources")
         # No project registered in the DB
         self.assertEqual(output.status_code, 404)
@@ -37,7 +37,7 @@ class PagureFlaskRepoViewHistoryFileSimpletests(tests.Modeltests):
         self.assertIn("<p>Project not found</p>", output_text)
 
     def test_view_history_file_no_git_repo(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         tests.create_projects(self.session)
 
         output = self.app.get("/test/history/sources")
@@ -45,7 +45,7 @@ class PagureFlaskRepoViewHistoryFileSimpletests(tests.Modeltests):
         self.assertEqual(output.status_code, 404)
 
     def test_view_history_file_no_git_content(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -61,10 +61,10 @@ class PagureFlaskRepoViewHistoryFileSimpletests(tests.Modeltests):
 
 
 class PagureFlaskRepoViewHistoryFiletests(tests.Modeltests):
-    """ Tests for view_history_file endpoint of the flask pagure app """
+    """Tests for view_history_file endpoint of the flask pagure app"""
 
     def setUp(self):
-        """ Set up the environment, ran before every tests. """
+        """Set up the environment, ran before every tests."""
         super(PagureFlaskRepoViewHistoryFiletests, self).setUp()
         self.regex = re.compile(r' <div class="list-group-item " id="c_')
         tests.create_projects(self.session)
@@ -87,7 +87,7 @@ class PagureFlaskRepoViewHistoryFiletests(tests.Modeltests):
         )
 
     def test_view_history_file_default_branch_master(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         output = self.app.get("/test/history/sources")
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
@@ -96,7 +96,7 @@ class PagureFlaskRepoViewHistoryFiletests(tests.Modeltests):
         self.assertEqual(len(data), 2)
 
     def test_view_history_file_default_branch_non_master(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         repo = pygit2.Repository(os.path.join(self.path, "repos", "test.git"))
         reference = repo.lookup_reference("refs/heads/feature").resolve()
         repo.set_head(reference.name)
@@ -108,7 +108,7 @@ class PagureFlaskRepoViewHistoryFiletests(tests.Modeltests):
         self.assertEqual(len(data), 3)
 
     def test_view_history_file_on_commit(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         repo_obj = pygit2.Repository(
             os.path.join(self.path, "repos", "test.git")
         )
@@ -125,7 +125,7 @@ class PagureFlaskRepoViewHistoryFiletests(tests.Modeltests):
         self.assertEqual(len(data), 1)
 
     def test_view_history_file_on_branch(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         output = self.app.get("/test/history/sources?identifier=feature")
         self.assertEqual(output.status_code, 200)
         output_text = output.get_data(as_text=True)
@@ -134,7 +134,7 @@ class PagureFlaskRepoViewHistoryFiletests(tests.Modeltests):
         self.assertEqual(len(data), 3)
 
     def test_view_history_file_on_tag(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         # set a tag on the head's parent commit
         repo_obj = pygit2.Repository(
             os.path.join(self.path, "repos", "test.git")
@@ -154,7 +154,7 @@ class PagureFlaskRepoViewHistoryFiletests(tests.Modeltests):
         self.assertEqual(len(data), 1)
 
     def test_view_history_file_binary(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         # Add binary content
         tests.add_binary_git_repo(
             os.path.join(self.path, "repos", "test.git"), "test.jpg"
@@ -165,7 +165,7 @@ class PagureFlaskRepoViewHistoryFiletests(tests.Modeltests):
         self.assertIn("<strong>Add a fake image file</strong>", output_text)
 
     def test_view_history_file_non_ascii_name(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         tests.add_commit_git_repo(
             os.path.join(self.path, "repos", "test.git"),
             ncommits=1,
@@ -181,7 +181,7 @@ class PagureFlaskRepoViewHistoryFiletests(tests.Modeltests):
         self.assertIn("<strong>Add row 0 to Šource file</strong>", output_text)
 
     def test_view_history_file_fork_of_a_fork(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
             name="test3",
@@ -216,14 +216,14 @@ class PagureFlaskRepoViewHistoryFiletests(tests.Modeltests):
         )
 
     def test_view_history_file_no_file(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         output = self.app.get("/test/history/foofile")
         self.assertEqual(output.status_code, 400)
         output_text = output.get_data(as_text=True)
         self.assertIn("No history could be found for this file", output_text)
 
     def test_view_history_file_folder(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         tests.add_commit_git_repo(
             os.path.join(self.path, "repos", "test.git/folder1"),
             ncommits=1,
@@ -235,7 +235,7 @@ class PagureFlaskRepoViewHistoryFiletests(tests.Modeltests):
         self.assertIn("No history could be found for this file", output_text)
 
     def test_view_history_file_existing_folder(self):
-        """ Test the view_history_file endpoint """
+        """Test the view_history_file endpoint"""
         tests.add_content_to_git(
             os.path.join(self.path, "repos", "test.git"), folders="foo/bar"
         )
diff --git a/tests/test_pagure_flask_ui_roadmap.py b/tests/test_pagure_flask_ui_roadmap.py
index 1270e1f..f7eaa3b 100644
--- a/tests/test_pagure_flask_ui_roadmap.py
+++ b/tests/test_pagure_flask_ui_roadmap.py
@@ -31,12 +31,12 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureFlaskRoadmaptests(tests.Modeltests):
-    """ Tests for the pagure's roadmap """
+    """Tests for the pagure's roadmap"""
 
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_ticket_with_no_roadmap(self, p_send_email, p_ugt):
-        """ Test creating a ticket without roadmap. """
+        """Test creating a ticket without roadmap."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -86,7 +86,7 @@ class PagureFlaskRoadmaptests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_ticket_with_roadmap(self, p_send_email, p_ugt):
-        """ Test creating a ticket with roadmap. """
+        """Test creating a ticket with roadmap."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -157,7 +157,7 @@ class PagureFlaskRoadmaptests(tests.Modeltests):
             )
 
     def test_update_milestones(self):
-        """ Test updating milestones of a repo. """
+        """Test updating milestones of a repo."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -427,7 +427,7 @@ class PagureFlaskRoadmaptests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_milestones_without_dates(self, p_send_email, p_ugt):
-        """ Test creating two milestones with no dates. """
+        """Test creating two milestones with no dates."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
@@ -480,7 +480,7 @@ class PagureFlaskRoadmaptests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_roadmap_ui(self, p_send_email, p_ugt):
-        """ Test viewing the roadmap of a repo. """
+        """Test viewing the roadmap of a repo."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
diff --git a/tests/test_pagure_flask_ui_slash_branch_name.py b/tests/test_pagure_flask_ui_slash_branch_name.py
index cbb2e11..169158a 100644
--- a/tests/test_pagure_flask_ui_slash_branch_name.py
+++ b/tests/test_pagure_flask_ui_slash_branch_name.py
@@ -30,11 +30,10 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureFlaskSlashInBranchtests(tests.SimplePagureTest):
-    """ Tests for flask application when the branch name contains a '/'.
-    """
+    """Tests for flask application when the branch name contains a '/'."""
 
     def set_up_git_repo(self):
-        """ Set up the git repo to play with. """
+        """Set up the git repo to play with."""
 
         # Create a git repo to play with
         gitrepo = os.path.join(self.path, "repos", "test.git")
@@ -99,7 +98,7 @@ class PagureFlaskSlashInBranchtests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_view_repo(self, send_email):
-        """ Test the view_repo endpoint when the git repo has no master
+        """Test the view_repo endpoint when the git repo has no master
         branch.
         """
         send_email.return_value = True
@@ -147,7 +146,7 @@ class PagureFlaskSlashInBranchtests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_view_commits(self, send_email):
-        """ Test the view_commits endpoint when the git repo has no
+        """Test the view_commits endpoint when the git repo has no
         master branch.
         """
         send_email.return_value = True
@@ -176,7 +175,7 @@ class PagureFlaskSlashInBranchtests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_view_file(self, send_email):
-        """ Test the view_file endpoint when the git repo has no
+        """Test the view_file endpoint when the git repo has no
         master branch.
         """
         send_email.return_value = True
@@ -236,7 +235,7 @@ class PagureFlaskSlashInBranchtests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_view_raw_file(self, send_email):
-        """ Test the view_raw_file endpoint when the git repo has no
+        """Test the view_raw_file endpoint when the git repo has no
         master branch.
         """
         send_email.return_value = True
@@ -272,7 +271,7 @@ class PagureFlaskSlashInBranchtests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_view_tree(self, send_email):
-        """ Test the view_tree endpoint when the git repo has no
+        """Test the view_tree endpoint when the git repo has no
         master branch.
         """
         send_email.return_value = True
@@ -317,7 +316,7 @@ class PagureFlaskSlashInBranchtests(tests.SimplePagureTest):
 
     @patch("pagure.lib.notify.send_email")
     def test_new_request_pull(self, send_email):
-        """ Test the new_request_pull endpoint when the git repo has no
+        """Test the new_request_pull endpoint when the git repo has no
         master branch.
         """
         send_email.return_value = True
diff --git a/tests/test_pagure_flask_ui_star_project.py b/tests/test_pagure_flask_ui_star_project.py
index 26e707e..ae5462c 100644
--- a/tests/test_pagure_flask_ui_star_project.py
+++ b/tests/test_pagure_flask_ui_star_project.py
@@ -26,14 +26,14 @@ import tests
 
 class TestStarProjectUI(tests.SimplePagureTest):
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(TestStarProjectUI, self).setUp()
 
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
     def _check_star_count(self, data, stars=1):
-        """ Check if the star count is correct or not """
+        """Check if the star count is correct or not"""
         output = self.app.get("/test/", data=data, follow_redirects=True)
         if stars == 1:
             self.assertIn(
@@ -49,15 +49,15 @@ class TestStarProjectUI(tests.SimplePagureTest):
             )
 
     def test_star_project_no_project(self):
-        """ Test the star_project endpoint. """
+        """Test the star_project endpoint."""
 
         # No such project
         output = self.app.post("/test42/star/1")
         self.assertEqual(output.status_code, 404)
 
     def test_star_project_no_csrf(self):
-        """ Test the star_project endpoint for the case when there
-        is no CSRF token given """
+        """Test the star_project endpoint for the case when there
+        is no CSRF token given"""
 
         user = tests.FakeUser()
         user.username = "pingou"
@@ -70,7 +70,7 @@ class TestStarProjectUI(tests.SimplePagureTest):
             self.assertEqual(output.status_code, 400)
 
     def test_star_project_invalid_star(self):
-        """ Test the star_project endpoint for invalid star """
+        """Test the star_project endpoint for invalid star"""
 
         user = tests.FakeUser()
         user.username = "pingou"
@@ -84,7 +84,7 @@ class TestStarProjectUI(tests.SimplePagureTest):
             self._check_star_count(data=data, stars=0)
 
     def test_star_project_valid_star(self):
-        """ Test the star_project endpoint for correct star """
+        """Test the star_project endpoint for correct star"""
 
         user = tests.FakeUser()
         user.username = "pingou"
@@ -115,7 +115,7 @@ class TestStarProjectUI(tests.SimplePagureTest):
             self._check_star_count(data=data, stars=0)
 
     def test_repo_stargazers(self):
-        """ Test the repo_stargazers endpoint of pagure.ui.repo """
+        """Test the repo_stargazers endpoint of pagure.ui.repo"""
 
         # make pingou star the project
         # first create pingou
@@ -174,7 +174,7 @@ class TestStarProjectUI(tests.SimplePagureTest):
         )
 
     def test_user_stars(self):
-        """ Test the user_stars endpoint of pagure.ui.app """
+        """Test the user_stars endpoint of pagure.ui.app"""
 
         # Check pingou's stars before
         output = self.app.get("/user/pingou/stars/")
diff --git a/tests/test_pagure_flask_util.py b/tests/test_pagure_flask_util.py
index 95902ab..d193a9f 100644
--- a/tests/test_pagure_flask_util.py
+++ b/tests/test_pagure_flask_util.py
@@ -25,10 +25,10 @@ import tests
 
 
 class PagureUtilSSHPatterntests(tests.Modeltests):
-    """ Tests for the ssh_urlpattern in pagure.util """
+    """Tests for the ssh_urlpattern in pagure.util"""
 
     def test_ssh_pattern_valid(self):
-        """ Test the ssh_urlpattern with valid patterns. """
+        """Test the ssh_urlpattern with valid patterns."""
         patterns = [
             "ssh://user@host.com/repo.git",
             "git+ssh://user@host.com/repo.git",
@@ -43,7 +43,7 @@ class PagureUtilSSHPatterntests(tests.Modeltests):
             self.assertIsNotNone(ssh_urlpattern.match(pattern))
 
     def test_ssh_pattern_invalid(self):
-        """ Test the ssh_urlpattern with invalid patterns. """
+        """Test the ssh_urlpattern with invalid patterns."""
         patterns = [
             "http://user@host.com/repo.git",
             "git+http://user@host.com/repo.git",
diff --git a/tests/test_pagure_hooks_pagure_hook.py b/tests/test_pagure_hooks_pagure_hook.py
index b351fd2..5123efa 100644
--- a/tests/test_pagure_hooks_pagure_hook.py
+++ b/tests/test_pagure_hooks_pagure_hook.py
@@ -25,10 +25,10 @@ import tests
 
 
 class PagureHooksPagureHooktests(tests.SimplePagureTest):
-    """ Tests for pagure.hooks.pagure_hook """
+    """Tests for pagure.hooks.pagure_hook"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureHooksPagureHooktests, self).setUp()
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
@@ -95,7 +95,7 @@ class PagureHooksPagureHooktests(tests.SimplePagureTest):
 
     @mock.patch("pagure.hooks.pagure_hook.fixes_relation")
     def test_generate_revision_change_log_short_url(self, fixes_relation):
-        """ Test generate_revision_change_log when the comment contains
+        """Test generate_revision_change_log when the comment contains
         a short link to the same project.
         """
 
@@ -127,7 +127,7 @@ class PagureHooksPagureHooktests(tests.SimplePagureTest):
 
     @mock.patch("pagure.hooks.pagure_hook.fixes_relation")
     def test_generate_revision_change_log_full_url(self, fixes_relation):
-        """ Test generate_revision_change_log when the comment contains
+        """Test generate_revision_change_log when the comment contains
         a full link to another project.
         """
 
@@ -163,7 +163,7 @@ class PagureHooksPagureHooktests(tests.SimplePagureTest):
 
     @mock.patch("pagure.hooks.pagure_hook.fixes_relation")
     def test_generate_revision_change_log_full_url_fork(self, fixes_relation):
-        """ Test generate_revision_change_log when the comment contains
+        """Test generate_revision_change_log when the comment contains
         a full link to a fork.
         """
 
diff --git a/tests/test_pagure_lib.py b/tests/test_pagure_lib.py
index 1076bb6..3401fa0 100644
--- a/tests/test_pagure_lib.py
+++ b/tests/test_pagure_lib.py
@@ -16,6 +16,7 @@ import shutil
 import sys
 import os
 
+import six
 import pygit2
 import markdown
 from mock import patch, MagicMock, Mock
@@ -405,12 +406,12 @@ class PagureLibtests_search_projects(tests.Modeltests):
 
 
 class PagureLibtests(tests.Modeltests):
-    """ Tests for pagure.lib """
+    """Tests for pagure.lib"""
 
     maxDiff = None
 
     def test_get_repotypes(self):
-        """ Test the get_repotypes function of pagure.lib.query. """
+        """Test the get_repotypes function of pagure.lib.query."""
         with patch.dict(
             pagure.config.config,
             {"ENABLE_TICKETS": False, "ENABLE_DOCS": False},
@@ -447,14 +448,14 @@ class PagureLibtests(tests.Modeltests):
             )
 
     def test_get_next_id(self):
-        """ Test the get_next_id function of pagure.lib.query. """
+        """Test the get_next_id function of pagure.lib.query."""
         tests.create_projects(self.session)
         self.assertEqual(1, pagure.lib.query.get_next_id(self.session, 1))
 
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_new_issue(self, p_send_email, p_ugt):
-        """ Test the new_issue of pagure.lib.query. """
+        """Test the new_issue of pagure.lib.query."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -547,7 +548,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_edit_issue(self, p_send_email, p_ugt):
-        """ Test the edit_issue of pagure.lib.query. """
+        """Test the edit_issue of pagure.lib.query."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -674,7 +675,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_edit_issue_close_status(self, p_send_email, p_ugt):
-        """ Test the edit_issue of pagure.lib.query. """
+        """Test the edit_issue of pagure.lib.query."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -727,8 +728,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_edit_issue_priority(self, p_send_email, p_ugt):
-        """ Test the edit_issue of pagure.lib when changing the priority.
-        """
+        """Test the edit_issue of pagure.lib when changing the priority."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -770,7 +770,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_edit_issue_depending(self, p_send_email, p_ugt):
-        """ Test the edit_issue of pagure.lib when the issue depends on
+        """Test the edit_issue of pagure.lib when the issue depends on
         another.
         """
         p_send_email.return_value = True
@@ -865,7 +865,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_add_issue_dependency(self):
-        """ Test the add_issue_dependency of pagure.lib.query. """
+        """Test the add_issue_dependency of pagure.lib.query."""
 
         self.test_new_issue()
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -917,7 +917,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_edit_comment(self, mock_redis):
-        """ Test the edit_issue of pagure.lib.query. """
+        """Test the edit_issue of pagure.lib.query."""
         mock_redis.return_value = True
 
         self.test_add_issue_comment()
@@ -954,7 +954,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_edit_comment_private(self, mock_redis):
-        """ Test the edit_issue of pagure.lib.query. """
+        """Test the edit_issue of pagure.lib.query."""
 
         self.test_add_issue_comment_private()
 
@@ -990,7 +990,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     @patch("pagure.lib.query.REDIS")
     def test_add_tag_obj(self, mock_redis):
-        """ Test the add_tag_obj of pagure.lib.query. """
+        """Test the add_tag_obj of pagure.lib.query."""
         mock_redis.return_value = True
 
         self.test_edit_issue()
@@ -1037,7 +1037,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_remove_tags(self, p_send_email, p_ugt):
-        """ Test the remove_tags of pagure.lib.query. """
+        """Test the remove_tags of pagure.lib.query."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1063,7 +1063,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_remove_tags_obj(self):
-        """ Test the remove_tags_obj of pagure.lib.query. """
+        """Test the remove_tags_obj of pagure.lib.query."""
 
         self.test_add_tag_obj()
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -1077,7 +1077,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_remove_tags_obj_from_project(self, p_send_email, p_ugt):
-        """ Test the remove_tags_obj of pagure.lib from a project. """
+        """Test the remove_tags_obj of pagure.lib from a project."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1109,7 +1109,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_edit_issue_tags(self, p_send_email, p_ugt):
-        """ Test the edit_issue_tags of pagure.lib.query. """
+        """Test the edit_issue_tags of pagure.lib.query."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1211,7 +1211,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_search_issues(self, p_send_email, p_ugt):
-        """ Test the search_issues of pagure.lib.query. """
+        """Test the search_issues of pagure.lib.query."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -1297,7 +1297,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_add_issue_assignee(self):
-        """ Test the add_issue_assignee of pagure.lib.query. """
+        """Test the add_issue_assignee of pagure.lib.query."""
 
         self.test_new_issue()
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -1398,7 +1398,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_add_issue_comment(self):
-        """ Test the add_issue_comment of pagure.lib.query. """
+        """Test the add_issue_comment of pagure.lib.query."""
 
         self.test_new_issue()
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -1437,7 +1437,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_add_issue_comment_private(self):
-        """ Test the add_issue_comment of pagure.lib.query. """
+        """Test the add_issue_comment of pagure.lib.query."""
         tests.create_projects(self.session)
         project = pagure.lib.query._get_project(self.session, "test")
 
@@ -1481,7 +1481,7 @@ class PagureLibtests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_add_user_to_project(self, p_send_email):
-        """ Test the add_user_to_project of pagure.lib.query. """
+        """Test the add_user_to_project of pagure.lib.query."""
         p_send_email.return_value = True
 
         tests.create_projects(self.session)
@@ -1538,7 +1538,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(repo.committers[0].user, "foo")
 
     def test_new_project(self):
-        """ Test the new_project of pagure.lib.query. """
+        """Test the new_project of pagure.lib.query."""
         gitfolder = os.path.join(self.path, "repos")
         docfolder = os.path.join(gitfolder, "docs")
         ticketfolder = os.path.join(gitfolder, "tickets")
@@ -1792,7 +1792,7 @@ class PagureLibtests(tests.Modeltests):
         )
 
     def test_new_project_user_ns(self):
-        """ Test the new_project of pagure.lib with user_ns on. """
+        """Test the new_project of pagure.lib with user_ns on."""
         gitfolder = os.path.join(self.path, "repos")
         docfolder = os.path.join(gitfolder, "docs")
         ticketfolder = os.path.join(gitfolder, "tickets")
@@ -1875,7 +1875,7 @@ class PagureLibtests(tests.Modeltests):
 
     @patch("pagure.lib.notify.log")
     def test_update_project_settings(self, mock_log):
-        """ Test the update_project_settings of pagure.lib.query. """
+        """Test the update_project_settings of pagure.lib.query."""
 
         tests.create_projects(self.session)
 
@@ -1973,7 +1973,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertFalse(repo.settings["pull_requests"])
 
     def test_search_issues_milestones_invalid(self):
-        """ Test the search_issues of pagure.lib.query. """
+        """Test the search_issues of pagure.lib.query."""
 
         self.test_edit_issue()
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -1990,7 +1990,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(len(issues), 2)
 
     def test_search_issues_custom_search(self):
-        """ Test the search_issues of pagure.lib.query. """
+        """Test the search_issues of pagure.lib.query."""
 
         self.test_edit_issue()
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -2002,7 +2002,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(len(issues), 0)
 
     def test_search_issues_offset(self):
-        """ Test the search_issues of pagure.lib.query. """
+        """Test the search_issues of pagure.lib.query."""
 
         self.test_edit_issue()
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -2016,7 +2016,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual([i.id for i in issues], [1])
 
     def test_search_issues_tags(self):
-        """ Test the search_issues of pagure.lib.query. """
+        """Test the search_issues of pagure.lib.query."""
 
         self.test_edit_issue()
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -2065,7 +2065,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual([tag.tag for tag in issues[0].tags], ["tag2"])
 
     def test_get_tags_of_project(self):
-        """ Test the get_tags_of_project of pagure.lib.query. """
+        """Test the get_tags_of_project of pagure.lib.query."""
 
         self.test_add_tag_obj()
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -2084,7 +2084,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual([tag.tag for tag in tags], [])
 
     def test_get_issue_statuses(self):
-        """ Test the get_issue_statuses of pagure.lib.query. """
+        """Test the get_issue_statuses of pagure.lib.query."""
         statuses = pagure.lib.query.get_issue_statuses(self.session)
         self.assertEqual(sorted(statuses), ["Closed", "Open"])
 
@@ -2092,7 +2092,7 @@ class PagureLibtests(tests.Modeltests):
         pagure.config.config, {"ALLOWED_EMAIL_DOMAINS": ["fp.o"]}, clear=True
     )
     def test_set_up_user(self):
-        """ Test the set_up_user of pagure.lib.query. """
+        """Test the set_up_user of pagure.lib.query."""
 
         items = pagure.lib.query.search_user(self.session)
         self.assertEqual(2, len(items))
@@ -2183,7 +2183,7 @@ class PagureLibtests(tests.Modeltests):
         clear=True,
     )
     def test_set_up_user_multiple_emaildomains(self):
-        """ Test the set_up_user of pagure.lib when there are
+        """Test the set_up_user of pagure.lib when there are
         multimple domains configured in ALLOWED_EMAIL_DOMAINS"""
 
         items = pagure.lib.query.search_user(self.session)
@@ -2238,7 +2238,7 @@ class PagureLibtests(tests.Modeltests):
         )
 
     def avatar_url_from_email(self):
-        """ Test the avatar_url_from_openid of pagure.lib.query. """
+        """Test the avatar_url_from_openid of pagure.lib.query."""
         output = pagure.lib.query.avatar_url_from_email(
             "pingou@fedoraproject.org"
         )
@@ -2258,7 +2258,7 @@ class PagureLibtests(tests.Modeltests):
         )
 
     def test_fork_project_with_branch(self):
-        """ Test the fork_project of pagure.lib.query. """
+        """Test the fork_project of pagure.lib.query."""
         gitfolder = os.path.join(self.path, "repos")
         docfolder = os.path.join(gitfolder, "docs")
         ticketfolder = os.path.join(gitfolder, "tickets")
@@ -2338,7 +2338,7 @@ class PagureLibtests(tests.Modeltests):
         )
 
     def test_fork_project_preserves_tags(self):
-        """ Test the fork_project of pagure.lib.query pushes tags to the fork. """
+        """Test the fork_project of pagure.lib.query pushes tags to the fork."""
         gitfolder = os.path.join(self.path, "repos")
         docfolder = os.path.join(gitfolder, "docs")
         ticketfolder = os.path.join(gitfolder, "tickets")
@@ -2417,7 +2417,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(tag.message, "release 1.2.3")
 
     def test_fork_project_namespaced(self):
-        """ Test the fork_project of pagure.lib on a namespaced project. """
+        """Test the fork_project of pagure.lib on a namespaced project."""
         gitfolder = os.path.join(self.path, "repos")
         docfolder = os.path.join(gitfolder, "docs")
         ticketfolder = os.path.join(gitfolder, "tickets")
@@ -2502,7 +2502,7 @@ class PagureLibtests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_new_pull_request(self, mockemail):
-        """ test new_pull_request of pagure.lib.query. """
+        """test new_pull_request of pagure.lib.query."""
         mockemail.return_value = True
 
         tests.create_projects(self.session)
@@ -2574,7 +2574,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.query.REDIS")
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_add_pull_request_comment(self, mock_redis):
-        """ Test add_pull_request_comment of pagure.lib.query. """
+        """Test add_pull_request_comment of pagure.lib.query."""
         mock_redis.return_value = True
 
         self.test_new_pull_request()
@@ -2606,7 +2606,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     @patch("pagure.lib.query.PAGURE_CI", MagicMock(return_value=True))
     def test_add_pull_request_comment_to_re_run_ci(self, mock_redis):
-        """ Test add_pull_request_comment of pagure.lib.query. """
+        """Test add_pull_request_comment of pagure.lib.query."""
         mock_redis.return_value = True
 
         self.test_new_pull_request()
@@ -2646,12 +2646,18 @@ class PagureLibtests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_add_pull_request_flag(self, mockemail):
-        """ Test add_pull_request_flag of pagure.lib.query. """
+        """Test add_pull_request_flag of pagure.lib.query."""
         mockemail.return_value = True
 
         self.test_new_pull_request()
         tests.create_tokens(self.session)
 
+        request = pagure.lib.query.search_pull_requests(
+            self.session, requestid=1
+        )
+        request.commit_stop = "hash_commit_stop"
+        self.session.add(request)
+        self.session.commit()
         request = pagure.lib.query.search_pull_requests(
             self.session, requestid=1
         )
@@ -2672,12 +2678,18 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(msg, ("Flag added", "jenkins_build_pagure_34"))
         self.session.commit()
 
-        self.assertEqual(len(request.flags), 1)
-        self.assertEqual(request.flags[0].token_id, "aaabbbcccddd")
+        # We're no longer adding a PR flag but instead a commit flag to the
+        # latest commit of the PR
+        self.assertEqual(len(request.flags), 0)
+        flags = pagure.lib.query.get_commit_flag(
+            self.session, request.project, "hash_commit_stop"
+        )
+        self.assertEqual(flags[0].comment, "Build passes")
+        self.assertEqual(flags[0].token_id, "aaabbbcccddd")
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_search_pull_requests(self):
-        """ Test search_pull_requests of pagure.lib.query. """
+        """Test search_pull_requests of pagure.lib.query."""
 
         self.test_new_pull_request()
 
@@ -2787,7 +2799,7 @@ class PagureLibtests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_close_pull_request(self, send_email):
-        """ Test close_pull_request of pagure.lib.query. """
+        """Test close_pull_request of pagure.lib.query."""
         send_email.return_value = True
 
         self.test_new_pull_request()
@@ -2826,7 +2838,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_remove_issue_dependency(self):
-        """ Test remove_issue_dependency of pagure.lib.query. """
+        """Test remove_issue_dependency of pagure.lib.query."""
 
         self.test_add_issue_dependency()
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -2884,7 +2896,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_get_issue_comment(self, p_send_email, p_ugt):
-        """ Test the get_issue_comment of pagure.lib.query. """
+        """Test the get_issue_comment of pagure.lib.query."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -2906,7 +2918,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_get_issue_by_uid(self, p_send_email, p_ugt):
-        """ Test the get_issue_by_uid of pagure.lib.query. """
+        """Test the get_issue_by_uid of pagure.lib.query."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -2925,7 +2937,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_update_tags(self, p_send_email, p_ugt):
-        """ Test the update_tags of pagure.lib.query. """
+        """Test the update_tags of pagure.lib.query."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -2970,7 +2982,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_update_dependency_issue(self, p_send_email, p_ugt):
-        """ Test the update_dependency_issue of pagure.lib.query. """
+        """Test the update_dependency_issue of pagure.lib.query."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3026,7 +3038,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_update_blocked_issue(self, p_send_email, p_ugt):
-        """ Test the update_blocked_issue of pagure.lib.query. """
+        """Test the update_blocked_issue of pagure.lib.query."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -3075,7 +3087,7 @@ class PagureLibtests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_add_pull_request_assignee(self, mockemail):
-        """ Test add_pull_request_assignee of pagure.lib.query. """
+        """Test add_pull_request_assignee of pagure.lib.query."""
         mockemail.return_value = True
 
         self.test_new_pull_request()
@@ -3115,7 +3127,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(msg, None)
 
     def test_search_pending_email(self):
-        """ Test search_pending_email of pagure.lib.query. """
+        """Test search_pending_email of pagure.lib.query."""
 
         self.assertEqual(
             pagure.lib.query.search_pending_email(self.session), None
@@ -3154,7 +3166,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(pend.token, "abcdef")
 
     def test_generate_hook_token(self):
-        """ Test generate_hook_token of pagure.lib.query. """
+        """Test generate_hook_token of pagure.lib.query."""
 
         tests.create_projects(self.session)
 
@@ -3174,7 +3186,7 @@ class PagureLibtests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email")
     def test_pull_request_score(self, mockemail):
-        """ Test PullRequest.score of pagure.lib.model. """
+        """Test PullRequest.score of pagure.lib.model."""
         mockemail.return_value = True
 
         self.test_new_pull_request()
@@ -3226,7 +3238,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(request.score, 2)
 
     def test_add_group(self):
-        """ Test the add_group method of pagure.lib.query. """
+        """Test the add_group method of pagure.lib.query."""
         groups = pagure.lib.query.search_groups(self.session)
         self.assertEqual(len(groups), 0)
         self.assertEqual(groups, [])
@@ -3342,7 +3354,7 @@ class PagureLibtests(tests.Modeltests):
         )
 
     def test_add_user_to_group(self):
-        """ Test the add_user_to_group method of pagure.lib.query. """
+        """Test the add_user_to_group method of pagure.lib.query."""
         self.test_add_group()
         group = pagure.lib.query.search_groups(self.session, group_name="foo")
         self.assertNotEqual(group, None)
@@ -3404,7 +3416,7 @@ class PagureLibtests(tests.Modeltests):
         )
 
     def test_is_group_member(self):
-        """ Test the is_group_member method of pagure.lib.query. """
+        """Test the is_group_member method of pagure.lib.query."""
         self.test_add_group()
 
         self.assertFalse(
@@ -3424,7 +3436,7 @@ class PagureLibtests(tests.Modeltests):
         )
 
     def test_get_user_group(self):
-        """ Test the get_user_group method of pagure.lib.query. """
+        """Test the get_user_group method of pagure.lib.query."""
 
         self.test_add_group()
 
@@ -3439,7 +3451,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(item, None)
 
     def test_get_group_types(self):
-        """ Test the get_group_types method of pagure.lib.query. """
+        """Test the get_group_types method of pagure.lib.query."""
 
         self.test_add_group()
 
@@ -3453,7 +3465,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(groups[1].group_type, "user")
 
     def test_search_groups(self):
-        """ Test the search_groups method of pagure.lib.query. """
+        """Test the search_groups method of pagure.lib.query."""
 
         self.assertEqual(pagure.lib.query.search_groups(self.session), [])
 
@@ -3508,7 +3520,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(groups.group_name, "foo")
 
     def test_delete_user_of_group(self):
-        """ Test the delete_user_of_group method of pagure.lib.query. """
+        """Test the delete_user_of_group method of pagure.lib.query."""
         self.test_add_user_to_group()
 
         groups = pagure.lib.query.search_groups(self.session)
@@ -3609,7 +3621,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(len(group.users), 1)
 
     def test_edit_group_info(self):
-        """ Test the edit_group_info method of pagure.lib.query. """
+        """Test the edit_group_info method of pagure.lib.query."""
         self.test_add_group()
         group = pagure.lib.query.search_groups(self.session, group_name="foo")
         self.assertNotEqual(group, None)
@@ -3674,7 +3686,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(msg, "Nothing changed")
 
     def test_add_group_to_project(self):
-        """ Test the add_group_to_project method of pagure.lib.query. """
+        """Test the add_group_to_project method of pagure.lib.query."""
         tests.create_projects(self.session)
         self.test_add_group()
 
@@ -3784,7 +3796,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(project.committer_groups[0].group_name, "bar")
 
     def test_update_watch_status(self):
-        """ Test the update_watch_status method of pagure.lib.query. """
+        """Test the update_watch_status method of pagure.lib.query."""
         tests.create_projects(self.session)
 
         project = pagure.lib.query._get_project(self.session, "test")
@@ -3840,7 +3852,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(msg, "Watch status reset")
 
     def test_get_watch_level_on_repo_invalid(self):
-        """ test the get_watch_level_on_repo method of pagure.lib.query. """
+        """test the get_watch_level_on_repo method of pagure.lib.query."""
 
         self.assertRaises(
             RuntimeError,
@@ -3853,7 +3865,7 @@ class PagureLibtests(tests.Modeltests):
         )
 
     def test_get_watch_level_on_repo(self):
-        """ Test the get_watch_level_on_repo method of pagure.lib.query. """
+        """Test the get_watch_level_on_repo method of pagure.lib.query."""
         tests.create_projects(self.session)
         self.test_add_group()
 
@@ -4023,7 +4035,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(watch_level, [])
 
     def test_user_watch_list(self):
-        """ test user watch list method of pagure.lib """
+        """test user watch list method of pagure.lib"""
 
         tests.create_projects(self.session)
 
@@ -4074,7 +4086,7 @@ class PagureLibtests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     @patch("pagure.pfmarkdown._commit_exists", MagicMock(return_value=True))
     def test_text2markdown(self):
-        """ Test the text2markdown method in pagure.lib.query. """
+        """Test the text2markdown method in pagure.lib.query."""
         pagure.config.config["TESTING"] = True
         pagure.config.config["SERVER_NAME"] = "localhost.localdomain"
 
@@ -4333,8 +4345,8 @@ class PagureLibtests(tests.Modeltests):
             '<div class="markdown"><p>but not someone@pingou.com</p></div>',
         ]
 
-        if mk_321:
-            print("**** Markdown 3.2.1+ behavior")
+        if mk_321 or six.PY3:
+            print("**** Markdown 3.2.1+ and Markdown on Python 3 behavior")
             expected.append(
                 # '[![Fedora_infinity_small.png]'
                 # '(/test/issue/raw/Fedora_infinity_small.png)]'
@@ -4391,7 +4403,7 @@ class PagureLibtests(tests.Modeltests):
                     self.assertEqual(html, expected[idx])
 
     def test_text2markdown_exception(self):
-        """ Test the text2markdown method in pagure.lib.query. """
+        """Test the text2markdown method in pagure.lib.query."""
 
         text = "test#1 bazinga!"
         expected_html = "test#1 bazinga!"
@@ -4401,7 +4413,7 @@ class PagureLibtests(tests.Modeltests):
             self.assertEqual(html, expected_html)
 
     def test_text2markdown_empty_string(self):
-        """ Test the text2markdown method in pagure.lib.query. """
+        """Test the text2markdown method in pagure.lib.query."""
 
         text = ""
         expected_html = ""
@@ -4410,7 +4422,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(html, expected_html)
 
     def test_get_access_levels(self):
-        """ Test the get_access_levels method in pagure.lib """
+        """Test the get_access_levels method in pagure.lib"""
 
         acls = pagure.lib.query.get_access_levels(self.session)
         self.assertEqual(
@@ -4418,8 +4430,7 @@ class PagureLibtests(tests.Modeltests):
         )
 
     def test_get_project_users(self):
-        """ Test the get_project_users method when combine is True
-        """
+        """Test the get_project_users method when combine is True"""
 
         tests.create_projects(self.session)
         project = pagure.lib.query.get_authorized_project(
@@ -4534,8 +4545,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(users[0].username, "foo")
 
     def test_get_project_users_combine_false(self):
-        """ Test the get_project_users method when combine is False
-        """
+        """Test the get_project_users method when combine is False"""
 
         tests.create_projects(self.session)
         project = pagure.lib.query.get_authorized_project(
@@ -4622,8 +4632,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(users[0].username, "foo")
 
     def test_get_project_groups(self):
-        """ Test the get_project_groups method when combine is True
-        """
+        """Test the get_project_groups method when combine is True"""
 
         # Create some projects
         tests.create_projects(self.session)
@@ -4754,8 +4763,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(project.groups[0].display_name, "Justice League")
 
     def test_get_project_groups_combine_false(self):
-        """ Test the get_project_groups method when combine is False
-        """
+        """Test the get_project_groups method when combine is False"""
 
         # Create some projects
         tests.create_projects(self.session)
@@ -4872,8 +4880,8 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(project.groups[0].display_name, "Justice League")
 
     def test_get_obj_access_user(self):
-        """ Test the get_obj_access method of pagure.lib
-        for model.User object """
+        """Test the get_obj_access method of pagure.lib
+        for model.User object"""
 
         # Create the projects
         tests.create_projects(self.session)
@@ -4936,8 +4944,8 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(access_obj.access, "ticket")
 
     def test_get_obj_access_group(self):
-        """ Test the get_obj_access method of pagure.lib
-        for model.PagureGroup object """
+        """Test the get_obj_access method of pagure.lib
+        for model.PagureGroup object"""
 
         # Create the projects
         tests.create_projects(self.session)
@@ -5014,7 +5022,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(access_obj.access, "ticket")
 
     def test_set_watch_obj(self):
-        """ Test the set_watch_obj method in pagure.lib """
+        """Test the set_watch_obj method in pagure.lib"""
         # Create the project ns/test
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
@@ -5079,7 +5087,7 @@ class PagureLibtests(tests.Modeltests):
         self.assertEqual(out, "You are no longer watching this issue")
 
     def test_tokenize_search_string(self):
-        """ Test the tokenize_search_string function. """
+        """Test the tokenize_search_string function."""
         # These are the tests performed to make sure we tokenize correctly.
         # This is in the form: input string, custom fields, remaining pattern
         tests = [
@@ -5103,7 +5111,7 @@ class PagureLibtests(tests.Modeltests):
             )
 
     def test_save_report(self):
-        """ Test the save_report function. """
+        """Test the save_report function."""
         # Create the projects
         tests.create_projects(self.session)
 
@@ -5145,7 +5153,7 @@ class PagureLibtests(tests.Modeltests):
         )
 
     def test_text2markdown_table(self):
-        """ Test the text2markdown function with a markdown table. """
+        """Test the text2markdown function with a markdown table."""
         try:
             md_version = markdown.__version__.version_info
         except AttributeError:
@@ -5194,7 +5202,7 @@ foo bar
             self.assertEqual(html, expected)
 
     def test_text2markdown_table_old_mk(self):
-        """ Test the text2markdown function with a markdown table using the old
+        """Test the text2markdown function with a markdown table using the old
         format where the orientation instruction are provided next to the column
         delimiter unlike what can be done with more recent version of markdown.
         """
@@ -5237,20 +5245,20 @@ foo bar
             self.assertEqual(html, expected)
 
     def test_set_redis(self):
-        """ Test the set_redis function of pagure.lib.query. """
+        """Test the set_redis function of pagure.lib.query."""
         self.assertIsNone(pagure.lib.query.REDIS)
         pagure.lib.query.set_redis("0.0.0.0", 6379, 0)
         self.assertIsNotNone(pagure.lib.query.REDIS)
 
     def test_set_pagure_ci(self):
-        """ Test the set_pagure_ci function of pagure.lib.query. """
+        """Test the set_pagure_ci function of pagure.lib.query."""
         # self.assertIn(pagure.lib.query.PAGURE_CI, [None, ['jenkins']])
         pagure.lib.query.set_pagure_ci(True)
         self.assertIsNotNone(pagure.lib.query.PAGURE_CI)
         self.assertTrue(pagure.lib.query.PAGURE_CI)
 
     def test_get_user_invalid_user(self):
-        """ Test the get_user function of pagure.lib.query. """
+        """Test the get_user function of pagure.lib.query."""
         self.assertRaises(
             pagure.exceptions.PagureException,
             pagure.lib.query.get_user,
@@ -5259,25 +5267,25 @@ foo bar
         )
 
     def test_get_user_username(self):
-        """ Test the get_user function of pagure.lib.query. """
+        """Test the get_user function of pagure.lib.query."""
         user = pagure.lib.query.get_user(self.session, "foo")
         self.assertEqual(user.username, "foo")
 
     def test_get_user_email(self):
-        """ Test the get_user function of pagure.lib.query. """
+        """Test the get_user function of pagure.lib.query."""
         user = pagure.lib.query.get_user(self.session, "bar@pingou.com")
         self.assertEqual(user.username, "pingou")
 
     def test_is_valid_ssh_key_empty(self):
-        """ Test the is_valid_ssh_key function of pagure.lib.query. """
+        """Test the is_valid_ssh_key function of pagure.lib.query."""
         self.assertIsNone(pagure.lib.query.is_valid_ssh_key(""))
 
     def test_is_valid_ssh_key_invalid(self):
-        """ Test the is_valid_ssh_key function of pagure.lib.query. """
+        """Test the is_valid_ssh_key function of pagure.lib.query."""
         self.assertEqual(pagure.lib.query.is_valid_ssh_key("nonsense"), False)
 
     def test_is_valid_ssh_key(self):
-        """ Test the is_valid_ssh_key function of pagure.lib.query. """
+        """Test the is_valid_ssh_key function of pagure.lib.query."""
         keyinfo = pagure.lib.query.is_valid_ssh_key(
             """ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDtgzSO9d1IrKdmyBFUvtAJPLgGOhp0lSySkWRSe+/+3KXYjSnsLnCJQlO5M7JfaXhtTHEow86rh4W9+FoJdzo5iocAwH5xPZ5ttHLy7VHgTzNMUeMgKpjy6bBOdPoGPPG4mo7QCMCRJdWBRDv4OSEMLU5jQAvC272YK2V8L918VQ== root@test"""
         )
@@ -5293,8 +5301,8 @@ foo bar
         self.assertEqual(key[3], "(RSA)")
 
     def test_create_deploykeys_ssh_keys_on_disk_empty(self):
-        """ Test the create_deploykeys_ssh_keys_on_disk function of
-        pagure.lib.query. """
+        """Test the create_deploykeys_ssh_keys_on_disk function of
+        pagure.lib.query."""
         self.assertIsNone(
             pagure.lib.query.create_deploykeys_ssh_keys_on_disk(None, None)
         )
@@ -5303,8 +5311,8 @@ foo bar
         )
 
     def test_create_deploykeys_ssh_keys_on_disk_nokey(self):
-        """ Test the create_deploykeys_ssh_keys_on_disk function of
-        pagure.lib.query. """
+        """Test the create_deploykeys_ssh_keys_on_disk function of
+        pagure.lib.query."""
         tests.create_projects(self.session)
         project = pagure.lib.query._get_project(self.session, "test")
 
@@ -5324,8 +5332,8 @@ foo bar
         "pagure.lib.query.is_valid_ssh_key", MagicMock(return_value="foo bar")
     )
     def test_create_deploykeys_ssh_keys_on_disk(self):
-        """ Test the create_deploykeys_ssh_keys_on_disk function of
-        pagure.lib.query. """
+        """Test the create_deploykeys_ssh_keys_on_disk function of
+        pagure.lib.query."""
         tests.create_projects(self.session)
         project = pagure.lib.query._get_project(self.session, "test")
 
@@ -5376,8 +5384,8 @@ foo bar
         MagicMock(return_value="\nfoo bar"),
     )
     def test_create_deploykeys_ssh_keys_on_disk_empty_first_key(self):
-        """ Test the create_deploykeys_ssh_keys_on_disk function of
-        pagure.lib.query. """
+        """Test the create_deploykeys_ssh_keys_on_disk function of
+        pagure.lib.query."""
         tests.create_projects(self.session)
         project = pagure.lib.query._get_project(self.session, "test")
 
@@ -5407,8 +5415,8 @@ foo bar
         )
 
     def test_create_deploykeys_ssh_keys_on_disk_invalid(self):
-        """ Test the create_deploykeys_ssh_keys_on_disk function of
-        pagure.lib.query. """
+        """Test the create_deploykeys_ssh_keys_on_disk function of
+        pagure.lib.query."""
         tests.create_projects(self.session)
         project = pagure.lib.query._get_project(self.session, "test")
 
@@ -5438,13 +5446,13 @@ foo bar
         )
 
     def test_create_user_ssh_keys_on_disk_none(self):
-        """ Test the create_user_ssh_keys_on_disk function of pagure.lib.query. """
+        """Test the create_user_ssh_keys_on_disk function of pagure.lib.query."""
         self.assertIsNone(
             pagure.lib.query.create_user_ssh_keys_on_disk(None, None)
         )
 
     def test_update_user_settings_invalid_user(self):
-        """ Test the update_user_settings function of pagure.lib.query. """
+        """Test the update_user_settings function of pagure.lib.query."""
         self.assertRaises(
             pagure.exceptions.PagureException,
             pagure.lib.query.update_user_settings,
@@ -5454,7 +5462,7 @@ foo bar
         )
 
     def test_update_user_settings_no_change(self):
-        """ Test the update_user_settings function of pagure.lib.query. """
+        """Test the update_user_settings function of pagure.lib.query."""
 
         # First update the setting
         msg = pagure.lib.query.update_user_settings(
@@ -5471,7 +5479,7 @@ foo bar
         self.assertEqual(msg, "Successfully edited your settings")
 
     def test_update_user_settings_no_data(self):
-        """ Test the update_user_settings function of pagure.lib.query. """
+        """Test the update_user_settings function of pagure.lib.query."""
 
         msg = pagure.lib.query.update_user_settings(
             session=self.session,
@@ -5481,7 +5489,7 @@ foo bar
         self.assertEqual(msg, "No settings to change")
 
     def test_update_user_settings(self):
-        """ Test the update_user_settings function of pagure.lib.query. """
+        """Test the update_user_settings function of pagure.lib.query."""
 
         msg = pagure.lib.query.update_user_settings(
             session=self.session,
@@ -5496,7 +5504,7 @@ foo bar
         clear=True,
     )
     def test_add_email_to_user_with_logs(self):
-        """ Test the add_email_to_user function of pagure.lib when there
+        """Test the add_email_to_user function of pagure.lib when there
         are log entries associated to the email added.
         """
         user = pagure.lib.query.search_user(self.session, username="pingou")
@@ -5564,7 +5572,7 @@ foo bar
         "pagure.lib.query.is_valid_ssh_key", MagicMock(return_value="foo bar")
     )
     def test_update_user_ssh_valid_key(self):
-        """ Test the update_user_ssh function of pagure.lib.query. """
+        """Test the update_user_ssh function of pagure.lib.query."""
         pagure.SESSION = self.session
 
         pagure.lib.query.update_user_ssh(
@@ -5578,7 +5586,7 @@ foo bar
         )
 
     def test_add_user_pending_email_existing_email(self):
-        """ Test the add_user_pending_email function of pagure.lib.query. """
+        """Test the add_user_pending_email function of pagure.lib.query."""
         user = pagure.lib.query.search_user(self.session, username="pingou")
 
         self.assertRaises(
@@ -5596,7 +5604,7 @@ foo bar
         clear=True,
     )
     def test_add_user_pending_email(self):
-        """ Test the add_user_pending_email function of pagure.lib.query. """
+        """Test the add_user_pending_email function of pagure.lib.query."""
         user = pagure.lib.query.search_user(self.session, username="pingou")
 
         self.assertEqual(len(user.emails), 2)
@@ -5623,7 +5631,7 @@ foo bar
         self.assertEqual(len(user.emails_pending), 1)
 
     def test_resend_pending_email_someone_else_email(self):
-        """ Test the resend_pending_email function of pagure.lib.query. """
+        """Test the resend_pending_email function of pagure.lib.query."""
         user = pagure.lib.query.search_user(self.session, username="pingou")
 
         self.assertRaises(
@@ -5635,7 +5643,7 @@ foo bar
         )
 
     def test_resend_pending_email_email_validated(self):
-        """ Test the resend_pending_email function of pagure.lib.query. """
+        """Test the resend_pending_email function of pagure.lib.query."""
         user = pagure.lib.query.search_user(self.session, username="pingou")
 
         self.assertRaises(
@@ -5647,7 +5655,7 @@ foo bar
         )
 
     def test_get_acls(self):
-        """ Test the get_acls function of pagure.lib.query. """
+        """Test the get_acls function of pagure.lib.query."""
         acls = pagure.lib.query.get_acls(self.session)
         self.assertEqual(
             sorted([a.name for a in acls]),
@@ -5655,7 +5663,9 @@ foo bar
                 "commit",
                 "commit_flag",
                 "create_branch",
+                "create_git_alias",
                 "create_project",
+                "delete_git_alias",
                 "fork_project",
                 "generate_acls_project",
                 "internal_access",
@@ -5667,6 +5677,7 @@ foo bar
                 "issue_update",
                 "issue_update_custom_fields",
                 "issue_update_milestone",
+                "modify_git_alias",
                 "modify_project",
                 "pull_request_assign",
                 "pull_request_close",
@@ -5683,14 +5694,14 @@ foo bar
         )
 
     def test_get_acls_restrict_one(self):
-        """ Test the get_acls function of pagure.lib.query. """
+        """Test the get_acls function of pagure.lib.query."""
         acls = pagure.lib.query.get_acls(
             self.session, restrict="create_project"
         )
         self.assertEqual([a.name for a in acls], ["create_project"])
 
     def test_get_acls_restrict_two(self):
-        """ Test the get_acls function of pagure.lib.query. """
+        """Test the get_acls function of pagure.lib.query."""
         acls = pagure.lib.query.get_acls(
             self.session, restrict=["create_project", "issue_create"]
         )
@@ -5699,7 +5710,7 @@ foo bar
         )
 
     def test_filter_img_src(self):
-        """ Test the filter_img_src function of pagure.lib.query. """
+        """Test the filter_img_src function of pagure.lib.query."""
         for name in ("alt", "height", "width", "class"):
             self.assertTrue(pagure.lib.query.filter_img_src(name, "caption"))
 
@@ -5724,13 +5735,13 @@ foo bar
         )
 
     def test_clean_input(self):
-        """ Test the clean_input function of pagure.lib.query. """
+        """Test the clean_input function of pagure.lib.query."""
         text = '<a href="/path" title="click me!">Click here</a>'
         output = pagure.lib.query.clean_input(text)
         self.assertEqual(output, text)
 
     def test_could_be_text(self):
-        """ Test the could_be_text function of pagure.lib.query. """
+        """Test the could_be_text function of pagure.lib.query."""
         self.assertTrue(pagure.lib.query.could_be_text(b"foo"))
         self.assertTrue(pagure.lib.query.could_be_text("fâö".encode("utf-8")))
         self.assertFalse(
@@ -5738,7 +5749,7 @@ foo bar
         )
 
     def test_set_custom_key_fields_empty(self):
-        """ Test the set_custom_key_fields function of pagure.lib.query. """
+        """Test the set_custom_key_fields function of pagure.lib.query."""
         tests.create_projects(self.session)
         project = pagure.lib.query._get_project(self.session, "test")
         self.assertIsNotNone(project)
@@ -5755,7 +5766,7 @@ foo bar
         self.assertEqual(msg, "List of custom fields updated")
 
     def test_set_custom_key_fields(self):
-        """ Test the set_custom_key_fields function of pagure.lib.query. """
+        """Test the set_custom_key_fields function of pagure.lib.query."""
         tests.create_projects(self.session)
         project = pagure.lib.query._get_project(self.session, "test")
         self.assertIsNotNone(project)
@@ -5798,7 +5809,7 @@ foo bar
 
     @patch("pagure.lib.query.REDIS")
     def test_set_custom_key_value_boolean(self, mock_redis):
-        """ Test the set_custom_key_value function of pagure.lib.query. """
+        """Test the set_custom_key_value function of pagure.lib.query."""
         mock_redis.return_value = True
 
         tests.create_projects(self.session)
@@ -5859,7 +5870,7 @@ foo bar
 
     @patch("pagure.lib.query.REDIS")
     def test_set_custom_key_value_boolean_private_issue(self, mock_redis):
-        """ Test the set_custom_key_value function of pagure.lib.query. """
+        """Test the set_custom_key_value function of pagure.lib.query."""
         mock_redis.return_value = True
 
         tests.create_projects(self.session)
@@ -5922,7 +5933,7 @@ foo bar
 
     @patch("pagure.lib.query.REDIS")
     def test_set_custom_key_value_text(self, mock_redis):
-        """ Test the set_custom_key_value function of pagure.lib.query. """
+        """Test the set_custom_key_value function of pagure.lib.query."""
         mock_redis.return_value = True
 
         tests.create_projects(self.session)
@@ -5981,7 +5992,7 @@ foo bar
         self.assertEqual(mock_redis.publish.call_count, 2)
 
     def test_log_action_invalid(self):
-        """ Test the log_action function of pagure.lib.query. """
+        """Test the log_action function of pagure.lib.query."""
         obj = MagicMock
         obj.isa = "invalid"
         self.assertRaises(
@@ -5994,7 +6005,7 @@ foo bar
         )
 
     def test_search_token_no_acls(self):
-        """ Test the search_token function of pagure.lib.query. """
+        """Test the search_token function of pagure.lib.query."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -6004,7 +6015,7 @@ foo bar
         self.assertEqual(out[0].id, "aaabbbcccddd")
 
     def test_search_token_single_acls(self):
-        """ Test the search_token function of pagure.lib.query. """
+        """Test the search_token function of pagure.lib.query."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -6014,7 +6025,7 @@ foo bar
         self.assertEqual(out[0].id, "aaabbbcccddd")
 
     def test_search_token_single_acls_user(self):
-        """ Test the search_token function of pagure.lib.query. """
+        """Test the search_token function of pagure.lib.query."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -6031,7 +6042,7 @@ foo bar
         self.assertEqual(len(out), 0)
 
     def test_search_token_single_acls_active(self):
-        """ Test the search_token function of pagure.lib.query. """
+        """Test the search_token function of pagure.lib.query."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -6048,7 +6059,7 @@ foo bar
         self.assertEqual(len(out), 0)
 
     def test_update_read_only_mode(self):
-        """ Test the update_read_only_mode method of pagure.lib """
+        """Test the update_read_only_mode method of pagure.lib"""
 
         tests.create_projects(self.session)
 
diff --git a/tests/test_pagure_lib_add_user_to_project.py b/tests/test_pagure_lib_add_user_to_project.py
index 0fe5e2c..ba3de2b 100644
--- a/tests/test_pagure_lib_add_user_to_project.py
+++ b/tests/test_pagure_lib_add_user_to_project.py
@@ -25,10 +25,10 @@ import tests
 
 
 class PagureLibAddUserToProjecttests(tests.Modeltests):
-    """ Tests for pagure.lib.query.add_user_to_project """
+    """Tests for pagure.lib.query.add_user_to_project"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureLibAddUserToProjecttests, self).setUp()
 
         tests.create_projects(self.session)
@@ -63,7 +63,7 @@ class PagureLibAddUserToProjecttests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_re_add_user_to_project_default(self):
-        """ Update an existing user but to the same access level. """
+        """Update an existing user but to the same access level."""
         repo = pagure.lib.query._get_project(self.session, "test")
 
         # Try adding the same user with the same access
@@ -79,8 +79,7 @@ class PagureLibAddUserToProjecttests(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_user_to_project_default(self):
-        """ Update an existing user without any required group membership.
-        """
+        """Update an existing user without any required group membership."""
         repo = pagure.lib.query._get_project(self.session, "test")
 
         # Update the access of the user
@@ -224,10 +223,10 @@ class PagureLibAddUserToProjecttests(tests.Modeltests):
 
 
 class PagureLibAddUserToProjectWithGrouptests(PagureLibAddUserToProjecttests):
-    """ Tests for pagure.lib.query.add_user_to_project """
+    """Tests for pagure.lib.query.add_user_to_project"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureLibAddUserToProjectWithGrouptests, self).setUp()
 
         # Create group
diff --git a/tests/test_pagure_lib_drop_issue.py b/tests/test_pagure_lib_drop_issue.py
index 54e3266..056f2a8 100644
--- a/tests/test_pagure_lib_drop_issue.py
+++ b/tests/test_pagure_lib_drop_issue.py
@@ -27,12 +27,12 @@ import tests
 
 
 class PagureLibDropIssuetests(tests.Modeltests):
-    """ Tests for pagure.lib.query.drop_issue """
+    """Tests for pagure.lib.query.drop_issue"""
 
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def setUp(self, p_send_email, p_ugt):
-        """ Create a couple of tickets and add tag to the project so we can
+        """Create a couple of tickets and add tag to the project so we can
         play with them later.
         """
         super(PagureLibDropIssuetests, self).setUp()
@@ -95,7 +95,7 @@ class PagureLibDropIssuetests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.lib.git._maybe_wait", tests.definitely_wait)
     def test_drop_issue(self, p_send_email, p_ugt):
-        """ Test the drop_issue of pagure.lib.query.
+        """Test the drop_issue of pagure.lib.query.
 
         We had an issue where we could not delete issue that had been tagged
         with this test, we create two issues, tag one of them and delete
@@ -135,7 +135,7 @@ class PagureLibDropIssuetests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.lib.git._maybe_wait", tests.definitely_wait)
     def test_drop_issue_two_issues_one_tag(self, p_send_email, p_ugt):
-        """ Test the drop_issue of pagure.lib.query.
+        """Test the drop_issue of pagure.lib.query.
 
         We had an issue where we could not delete issue that had been tagged
         with this test, we create two issues, tag them both and delete one
diff --git a/tests/test_pagure_lib_encoding_utils.py b/tests/test_pagure_lib_encoding_utils.py
index ccc8825..dad98c9 100644
--- a/tests/test_pagure_lib_encoding_utils.py
+++ b/tests/test_pagure_lib_encoding_utils.py
@@ -5,11 +5,18 @@ Tests for :module:`pagure.lib.encoding_utils`.
 
 from __future__ import unicode_literals, absolute_import
 
-import chardet
 import os
 import unittest
 import sys
 
+cchardet = None
+try:
+    import cchardet
+except ImportError:
+    pass
+
+import chardet
+
 sys.path.insert(
     0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
 )
@@ -24,7 +31,10 @@ class TestGuessEncoding(unittest.TestCase):
         """
         data = "Twas bryllyg, and the slythy toves did gyre and gymble"
         result = encoding_utils.guess_encoding(data.encode("ascii"))
-        self.assertEqual(result, "ascii")
+        if cchardet is not None:
+            self.assertEqual(result, "ASCII")
+        else:
+            self.assertEqual(result, "ascii")
 
     def test_guess_encoding_favor_utf_8(self):
         """
@@ -36,37 +46,44 @@ class TestGuessEncoding(unittest.TestCase):
         data = "Šabata".encode("utf-8")
         result = encoding_utils.guess_encoding(data)
         chardet_result = chardet.detect(data)
-        self.assertEqual(result, "utf-8")
-        if chardet.__version__[0] == "3":
-            self.assertEqual(chardet_result["encoding"], "ISO-8859-9")
+        if cchardet:
+            self.assertEqual(result, "WINDOWS-1250")
         else:
-            self.assertEqual(chardet_result["encoding"], "ISO-8859-2")
+            self.assertEqual(result, "utf-8")
+            if chardet.__version__[0] in ("3", "4"):
+                self.assertEqual(chardet_result["encoding"], "ISO-8859-9")
+            else:
+                self.assertEqual(chardet_result["encoding"], "ISO-8859-2")
 
     def test_guess_encoding_no_data(self):
-        """ Test encoding_utils.guess_encoding() with an empty string """
+        """Test encoding_utils.guess_encoding() with an empty string"""
         result = encoding_utils.guess_encoding("".encode("utf-8"))
         self.assertEqual(result, "ascii")
 
 
 class TestGuessEncodings(unittest.TestCase):
     def test_guess_encodings(self):
-        """ Test the encoding_utils.guess_encodings() method. """
+        """Test the encoding_utils.guess_encodings() method."""
         data = "Šabata".encode("utf-8")
         result = encoding_utils.guess_encodings(data)
         chardet_result = chardet.detect(data)
-        if chardet.__version__[0] == "3":
-            # The first three have different confidence values
-            self.assertListEqual(
-                [encoding.encoding for encoding in result][:3],
-                ["utf-8", "ISO-8859-9", "ISO-8859-1"],
-            )
-            # This is the one with the least confidence
-            self.assertEqual(result[-1].encoding, "windows-1255")
-            # The values in the middle of the list all have the same confidence
-            # value and can't be sorted reliably: use sets.
-            self.assertEqual(
-                set([encoding.encoding for encoding in result]),
-                set(
+        if cchardet is not None:
+            # The last one in the list (which apparently has only one)
+            self.assertEqual(result[-1].encoding, "WINDOWS-1250")
+        else:
+            if chardet.__version__[0] in ("3", "4"):
+                # The first three have different confidence values
+                expexted_list = ["utf-8", "ISO-8859-9", "ISO-8859-1"]
+                # This is the one with the least confidence
+                self.assertEqual(result[-1].encoding, "windows-1255")
+                self.assertListEqual(
+                    [encoding.encoding for encoding in result][:3],
+                    expexted_list,
+                )
+
+                # The values in the middle of the list all have the same confidence
+                # value and can't be sorted reliably: use sets.
+                expected_list = sorted(
                     [
                         "utf-8",
                         "ISO-8859-9",
@@ -89,26 +106,34 @@ class TestGuessEncodings(unittest.TestCase):
                         "windows-1251",
                         "windows-1255",
                     ]
-                ),
-            )
-            self.assertEqual(chardet_result["encoding"], "ISO-8859-9")
-        else:
-            self.assertListEqual(
-                [encoding.encoding for encoding in result],
-                ["utf-8", "ISO-8859-2", "windows-1252"],
-            )
-            self.assertEqual(chardet_result["encoding"], "ISO-8859-2")
+                )
+                self.assertListEqual(
+                    sorted(set([encoding.encoding for encoding in result])),
+                    expected_list,
+                )
+                self.assertEqual(chardet_result["encoding"], "ISO-8859-9")
+            else:
+                self.assertListEqual(
+                    [encoding.encoding for encoding in result],
+                    ["utf-8", "ISO-8859-2", "windows-1252"],
+                )
+                self.assertEqual(chardet_result["encoding"], "ISO-8859-2")
 
     def test_guess_encodings_no_data(self):
-        """ Test encoding_utils.guess_encodings() with an emtpy string """
+        """Test encoding_utils.guess_encodings() with an emtpy string"""
         result = encoding_utils.guess_encodings("".encode("utf-8"))
         self.assertEqual([encoding.encoding for encoding in result], ["ascii"])
 
 
 class TestDecode(unittest.TestCase):
     def test_decode(self):
-        """ Test encoding_utils.decode() """
-        data = "Šabata"
+        """Test encoding_utils.decode()"""
+        data = (
+            "This is a little longer text for testing Šabata's encoding. "
+            "With more characters, let's see if it become more clear as to what "
+            "encoding should be used for this. We'll include from french words "
+            "in there for non-ascii: français, gagné!"
+        )
         self.assertEqual(data, encoding_utils.decode(data.encode("utf-8")))
 
 
diff --git a/tests/test_pagure_lib_git.py b/tests/test_pagure_lib_git.py
index fd73e1b..57eace9 100644
--- a/tests/test_pagure_lib_git.py
+++ b/tests/test_pagure_lib_git.py
@@ -33,11 +33,13 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureLibGittests(tests.Modeltests):
-    """ Tests for pagure.lib.git """
+    """Tests for pagure.lib.git"""
+
+    maxDiff = None
 
     def test_write_gitolite_acls(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git.
-        when the new uesr is an made an admin """
+        """Test the write_gitolite_acls function of pagure.lib.git.
+        when the new uesr is an made an admin"""
         tests.create_projects(self.session)
 
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -141,8 +143,8 @@ repo requests/forks/pingou/test3
         self.assertFalse(os.path.exists(outputconf))
 
     def test_write_gitolite_acls_preconf(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git with
-        a preconf set """
+        """Test the write_gitolite_acls function of pagure.lib.git with
+        a preconf set"""
         tests.create_projects(self.session)
 
         outputconf = os.path.join(self.path, "test_gitolite.conf")
@@ -212,8 +214,8 @@ repo requests/somenamespace/test3
         self.assertFalse(os.path.exists(outputconf))
 
     def test_write_gitolite_acls_preconf_postconf(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git with
-        a postconf set """
+        """Test the write_gitolite_acls function of pagure.lib.git with
+        a postconf set"""
         tests.create_projects(self.session)
 
         outputconf = os.path.join(self.path, "test_gitolite.conf")
@@ -293,8 +295,8 @@ repo requests/somenamespace/test3
         self.assertFalse(os.path.exists(outputconf))
 
     def test_write_gitolite_acls_postconf(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git with
-        a preconf and a postconf set """
+        """Test the write_gitolite_acls function of pagure.lib.git with
+        a preconf and a postconf set"""
         tests.create_projects(self.session)
 
         outputconf = os.path.join(self.path, "test_gitolite.conf")
@@ -363,7 +365,7 @@ repo requests/somenamespace/test3
         self.assertFalse(os.path.exists(outputconf))
 
     def test_write_gitolite_acls_deploykeys(self):
-        """ Test write_gitolite_acls function to add deploy keys. """
+        """Test write_gitolite_acls function to add deploy keys."""
         tests.create_projects(self.session)
 
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -481,8 +483,8 @@ repo requests/forks/pingou/test3
         self.assertFalse(os.path.exists(outputconf))
 
     def test_write_gitolite_acls_ticket(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git.
-        when the new uesr is just a ticketer """
+        """Test the write_gitolite_acls function of pagure.lib.git.
+        when the new uesr is just a ticketer"""
         tests.create_projects(self.session)
 
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -584,8 +586,8 @@ repo requests/forks/pingou/test3
         self.assertFalse(os.path.exists(outputconf))
 
     def test_write_gitolite_acls_commit(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git.
-        when the new uesr is just a committer """
+        """Test the write_gitolite_acls function of pagure.lib.git.
+        when the new uesr is just a committer"""
         tests.create_projects(self.session)
 
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -691,7 +693,7 @@ repo requests/forks/pingou/test3
         self.assertFalse(os.path.exists(outputconf))
 
     def test_write_gitolite_acls_groups(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git with
+        """Test the write_gitolite_acls function of pagure.lib.git with
         groups as admin
         """
         tests.create_projects(self.session)
@@ -845,7 +847,7 @@ repo requests/forks/pingou/test2
         self.assertFalse(os.path.exists(outputconf))
 
     def test_write_gitolite_acls_groups_ticket(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git with
+        """Test the write_gitolite_acls function of pagure.lib.git with
         groups as ticketers
         """
         tests.create_projects(self.session)
@@ -1000,7 +1002,7 @@ repo requests/forks/pingou/test2
         self.assertFalse(os.path.exists(outputconf))
 
     def test_write_gitolite_acls_groups_commit(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git with
+        """Test the write_gitolite_acls function of pagure.lib.git with
         groups as committers
         """
         tests.create_projects(self.session)
@@ -1159,7 +1161,7 @@ repo requests/forks/pingou/test2
         self.assertFalse(os.path.exists(outputconf))
 
     def test_write_gitolite_project_pr_only(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git.
+        """Test the write_gitolite_acls function of pagure.lib.git.
         when the project enforces the PR approach.
         """
         tests.create_projects(self.session)
@@ -1266,7 +1268,7 @@ repo requests/forks/pingou/test3
 
     @patch.dict("pagure.config.config", {"PR_ONLY": True})
     def test_write_gitolite_global_pr_only(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git.
+        """Test the write_gitolite_acls function of pagure.lib.git.
         when the pagure instance enforces the PR approach.
         """
         tests.create_projects(self.session)
@@ -1360,7 +1362,7 @@ repo requests/forks/pingou/test3
 
     @patch("pagure.lib.notify.send_email")
     def test_update_git(self, email_f):
-        """ Test the update_git of pagure.lib.git. """
+        """Test the update_git of pagure.lib.git."""
         email_f.return_value = True
 
         # Create project
@@ -1411,7 +1413,7 @@ new file mode 100644
 index 0000000..60f7480
 --- /dev/null
 +++ b/456
-@@ -0,0 +1,31 @@
+@@ -0,0 +1,33 @@
 +{
 +    "assignee": null,
 +    "blocks": [],
@@ -1422,6 +1424,7 @@ index 0000000..60f7480
 +    "custom_fields": [],
 +    "date_created": null,
 +    "depends": [],
++    "full_url": "http://localhost.localdomain/test_ticket_repo/issue/1",
 +    "id": 1,
 +    "last_updated": null,
 +    "milestone": null,
@@ -1437,6 +1440,7 @@ index 0000000..60f7480
 +            "bar@pingou.com",
 +            "foo@pingou.com"
 +        ],
++        "full_url": "http://localhost.localdomain/user/pingou",
 +        "fullname": "PY C",
 +        "name": "pingou",
 +        "url_path": "user/pingou"
@@ -1513,7 +1517,7 @@ diff --git a/123 b/456
 index 458821a..77674a8
 --- a/123
 +++ b/456
-@@ -4,13 +4,33 @@
+@@ -4,14 +4,35 @@
      "close_status": null,
      "closed_at": null,
 -    "comments": [],
@@ -1532,6 +1536,7 @@ index 458821a..77674a8
 +                "emails": [
 +                    "foo@bar.com"
 +                ],
++                "full_url": "http://localhost.localdomain/user/foo",
 +                "fullname": "foo bar",
 +                "name": "foo",
 +                "url_path": "user/foo"
@@ -1542,6 +1547,7 @@ index 458821a..77674a8
      "custom_fields": [],
      "date_created": null,
      "depends": [],
+     "full_url": "http://localhost.localdomain/test_ticket_repo/issue/1",
      "id": 1,
 -    "last_updated": "<date>",
 +    "last_updated": "<date>",
@@ -1588,7 +1594,7 @@ index 458821a..77674a8
         self.assertEqual(commit_patch, exp)
 
     def test_clean_git(self):
-        """ Test the clean_git method of pagure.lib.git. """
+        """Test the clean_git method of pagure.lib.git."""
         self.test_update_git()
 
         gitpath = os.path.join(
@@ -1622,7 +1628,7 @@ index 458821a..77674a8
 
     @patch("pagure.lib.notify.send_email")
     def test_update_git_requests(self, email_f):
-        """ Test the update_git of pagure.lib.git for pull-requests. """
+        """Test the update_git of pagure.lib.git for pull-requests."""
         email_f.return_value = True
 
         # Create project
@@ -1683,7 +1689,7 @@ new file mode 100644
 index 0000000..60f7480
 --- /dev/null
 +++ b/456
-@@ -0,0 +1,156 @@
+@@ -0,0 +1,162 @@
 +{
 +    "assignee": null,
 +    "branch": "master",
@@ -1695,6 +1701,7 @@ index 0000000..60f7480
 +    "commit_start": null,
 +    "commit_stop": null,
 +    "date_created": null,
++    "full_url": "http://localhost.localdomain/test_ticket_repo/pull-request/1",
 +    "id": 1,
 +    "initial_comment": null,
 +    "last_updated": null,
@@ -1720,6 +1727,7 @@ index 0000000..60f7480
 +        "date_created": null,
 +        "date_modified": null,
 +        "description": "test project for ticket",
++        "full_url": "http://localhost.localdomain/test_ticket_repo",
 +        "fullname": "test_ticket_repo",
 +        "id": 1,
 +        "milestones": {},
@@ -1755,6 +1763,7 @@ index 0000000..60f7480
 +                "bar@pingou.com",
 +                "foo@pingou.com"
 +            ],
++            "full_url": "http://localhost.localdomain/user/pingou",
 +            "fullname": "PY C",
 +            "name": "pingou",
 +            "url_path": "user/pingou"
@@ -1783,6 +1792,7 @@ index 0000000..60f7480
 +        "date_created": null,
 +        "date_modified": null,
 +        "description": "test project for ticket",
++        "full_url": "http://localhost.localdomain/test_ticket_repo",
 +        "fullname": "test_ticket_repo",
 +        "id": 1,
 +        "milestones": {},
@@ -1818,6 +1828,7 @@ index 0000000..60f7480
 +                "bar@pingou.com",
 +                "foo@pingou.com"
 +            ],
++            "full_url": "http://localhost.localdomain/user/pingou",
 +            "fullname": "PY C",
 +            "name": "pingou",
 +            "url_path": "user/pingou"
@@ -1835,6 +1846,7 @@ index 0000000..60f7480
 +            "bar@pingou.com",
 +            "foo@pingou.com"
 +        ],
++        "full_url": "http://localhost.localdomain/user/pingou",
 +        "fullname": "PY C",
 +        "name": "pingou",
 +        "url_path": "user/pingou"
@@ -1880,7 +1892,7 @@ index 0000000..60f7480
         self.assertEqual(patch, exp)
 
     def test_update_ticket_from_git_no_priority(self):
-        """ Test the update_ticket_from_git method from pagure.lib.git. """
+        """Test the update_ticket_from_git method from pagure.lib.git."""
         tests.create_projects(self.session)
 
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -1895,7 +1907,11 @@ index 0000000..60f7480
             "comments": [],
             "content": "bar",
             "date_created": "1426500263",
-            "user": {"name": "pingou", "emails": ["pingou@fedoraproject.org"]},
+            "user": {
+                "name": "pingou",
+                "emails": ["pingou@fedoraproject.org"],
+                "full_url": "http://localhost.localdomain/user/pingou",
+            },
             "milestone": "Next Release",
             "priority": 1,
         }
@@ -1920,7 +1936,11 @@ index 0000000..60f7480
             "comments": [],
             "content": "bar",
             "date_created": "1426500263",
-            "user": {"name": "pingou", "emails": ["pingou@fedoraproject.org"]},
+            "user": {
+                "name": "pingou",
+                "emails": ["pingou@fedoraproject.org"],
+                "full_url": "http://localhost.localdomain/user/pingou",
+            },
             "milestone": "Next Release",
         }
 
@@ -1942,7 +1962,11 @@ index 0000000..60f7480
             "comments": [],
             "content": "bar",
             "date_created": "1426500263",
-            "user": {"name": "pingou", "emails": ["pingou@fedoraproject.org"]},
+            "user": {
+                "name": "pingou",
+                "emails": ["pingou@fedoraproject.org"],
+                "full_url": "http://localhost.localdomain/user/pingou",
+            },
             "milestone": "Next Release",
             "priority": 1,
         }
@@ -1973,7 +1997,7 @@ index 0000000..60f7480
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_update_ticket_from_git_close_ticket(self):
-        """ Test the update_ticket_from_git method from pagure.lib.git. """
+        """Test the update_ticket_from_git method from pagure.lib.git."""
         tests.create_projects(self.session)
 
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -1989,7 +2013,11 @@ index 0000000..60f7480
             "comments": [],
             "content": "bar",
             "date_created": "1426500263",
-            "user": {"name": "foo", "emails": ["foo@fedoraproject.org"]},
+            "user": {
+                "name": "foo",
+                "emails": ["foo@fedoraproject.org"],
+                "full_url": "http://localhost.localdomain/user/pingou",
+            },
             "milestone": "Next Release",
         }
 
@@ -2012,7 +2040,11 @@ index 0000000..60f7480
             "comments": [],
             "content": "bar",
             "date_created": "1426500263",
-            "user": {"name": "foo", "emails": ["foo@fedoraproject.org"]},
+            "user": {
+                "name": "foo",
+                "emails": ["foo@fedoraproject.org"],
+                "full_url": "http://localhost.localdomain/user/pingou",
+            },
             "milestone": "Next Release",
         }
 
@@ -2049,7 +2081,7 @@ index 0000000..60f7480
         )
 
     def test_update_ticket_from_git(self):
-        """ Test the update_ticket_from_git method from pagure.lib.git. """
+        """Test the update_ticket_from_git method from pagure.lib.git."""
         tests.create_projects(self.session)
 
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -2068,7 +2100,11 @@ index 0000000..60f7480
             "comments": [],
             "content": "bar",
             "date_created": "1426500263",
-            "user": {"name": "pingou", "emails": ["pingou@fedoraproject.org"]},
+            "user": {
+                "name": "pingou",
+                "emails": ["pingou@fedoraproject.org"],
+                "full_url": "http://localhost.localdomain/user/pingou",
+            },
             "milestone": "Next Release",
             "priority": 1,
         }
@@ -2137,6 +2173,7 @@ index 0000000..60f7480
             "close_status": "Fixed",
             "closed_at": "1426595225",
             "title": "Rename pagure",
+            "full_url": "http://localhost.localdomain/test/issue/20",
             "private": False,
             "content": "This is too much of a conflict with the book",
             "user": {
@@ -2144,6 +2181,7 @@ index 0000000..60f7480
                 "name": "pingou",
                 "default_email": "pingou@fedoraproject.org",
                 "emails": ["pingou@fedoraproject.org"],
+                "full_url": "http://localhost.localdomain/user/pingou",
             },
             "id": 20,
             "blocks": [1],
@@ -2168,6 +2206,7 @@ index 0000000..60f7480
                         "name": "pingou",
                         "default_email": "pingou@fedoraproject.org",
                         "emails": ["pingou@fedoraproject.org"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                 },
                 {
@@ -2186,6 +2225,7 @@ index 0000000..60f7480
                         "name": "ralph",
                         "default_email": "ralph@fedoraproject.org",
                         "emails": ["ralph@fedoraproject.org"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                 },
             ],
@@ -2228,7 +2268,7 @@ index 0000000..60f7480
         )
 
     def test_update_request_from_git(self):
-        """ Test the update_request_from_git method from pagure.lib.git. """
+        """Test the update_request_from_git method from pagure.lib.git."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
 
@@ -2262,9 +2302,11 @@ index 0000000..60f7480
                     "name": "pingou",
                     "default_email": "pingou@fedoraproject.org",
                     "emails": ["pingou@fedoraproject.org"],
+                    "full_url": "http://localhost.localdomain/user/pingou",
                 },
                 "id": 1,
                 "description": "test project",
+                "full_url": "http://localhost.localdomain/test",
             },
             "commit_stop": "eface8e13bc2a08a3fb22af9a72a8c90e36b8b89",
             "user": {
@@ -2272,6 +2314,7 @@ index 0000000..60f7480
                 "name": "pingou",
                 "default_email": "pingou@fedoraproject.org",
                 "emails": ["pingou@fedoraproject.org"],
+                "full_url": "http://localhost.localdomain/user/pingou",
             },
             "id": 7,
             "comments": [
@@ -2282,6 +2325,7 @@ index 0000000..60f7480
                         "name": "pingou",
                         "default_email": "pingou@fedoraproject.org",
                         "emails": ["pingou@fedoraproject.org"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                     "parent": None,
                     "date_created": "1426843778",
@@ -2297,6 +2341,7 @@ index 0000000..60f7480
                         "name": "pingou",
                         "default_email": "pingou@fedoraproject.org",
                         "emails": ["pingou@fedoraproject.org"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                     "parent": None,
                     "date_created": "1426866781",
@@ -2312,6 +2357,7 @@ index 0000000..60f7480
                         "name": "pingou",
                         "default_email": "pingou@fedoraproject.org",
                         "emails": ["pingou@fedoraproject.org"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                     "parent": None,
                     "date_created": "1426866950",
@@ -2336,6 +2382,7 @@ index 0000000..60f7480
                         "name": "pingou",
                         "default_email": "pingou@fedoraproject.org",
                         "emails": ["pingou@fedoraproject.org"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                     "settings": {
                         "issue_tracker": True,
@@ -2344,6 +2391,7 @@ index 0000000..60f7480
                     },
                     "id": 1,
                     "description": "test project",
+                    "full_url": "http://localhost.localdomain/test",
                 },
                 "settings": {
                     "issue_tracker": True,
@@ -2359,7 +2407,9 @@ index 0000000..60f7480
                     "name": "fake",
                     "default_email": "fake@fedoraproject.org",
                     "emails": ["fake@fedoraproject.org"],
+                    "full_url": "http://localhost.localdomain/user/fake",
                 },
+                "full_url": "http://localhost.localdomain/fork/fake/test",
                 "id": 6,
                 "description": "test project",
             },
@@ -2405,12 +2455,14 @@ index 0000000..60f7480
                 "name": "test",
                 "custom_keys": [],
                 "date_created": "1426500194",
+                "full_url": "http://localhost.localdomain/test",
                 "tags": [],
                 "user": {
                     "fullname": "Pierre-YvesChibon",
                     "name": "pingou",
                     "default_email": "pingou@fedoraproject.org",
                     "emails": ["pingou@fedoraproject.org"],
+                    "full_url": "http://localhost.localdomain/user/pingou",
                 },
                 "settings": {
                     "issue_tracker": True,
@@ -2426,6 +2478,7 @@ index 0000000..60f7480
                 "name": "pingou",
                 "default_email": "pingou@fedoraproject.org",
                 "emails": ["pingou@fedoraproject.org"],
+                "full_url": "http://localhost.localdomain/user/pingou",
             },
             "id": 4,
             "comments": [],
@@ -2436,6 +2489,7 @@ index 0000000..60f7480
                 "parent": {
                     "parent": None,
                     "name": "test",
+                    "full_url": "http://localhost.localdomain/test",
                     "custom_keys": [],
                     "date_created": "1426500194",
                     "tags": [],
@@ -2444,6 +2498,7 @@ index 0000000..60f7480
                         "name": "pingou",
                         "default_email": "pingou@fedoraproject.org",
                         "emails": ["pingou@fedoraproject.org"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                     "settings": {
                         "issue_tracker": True,
@@ -2467,10 +2522,12 @@ index 0000000..60f7480
                     "name": "fake",
                     "default_email": "fake@fedoraproject.org",
                     "emails": ["fake@fedoraproject.org"],
+                    "full_url": "http://localhost.localdomain/user/fake",
                 },
                 "project_docs": True,
                 "id": 6,
                 "description": "test project",
+                "full_url": "http://localhost.localdomain/fork/fake/test",
             },
             "branch": "master",
             "date_created": "1426843745",
@@ -2505,9 +2562,11 @@ index 0000000..60f7480
         data = {
             "status": True,
             "uid": "d4182a2ac2d541d884742d3037c26e58",
+            "full_url": "http://localhost.localdomain/test/pull-request/5",
             "project": {
                 "parent": None,
                 "name": "test3",
+                "full_url": "http://localhost.localdomain/somenamespace/test3",
                 "custom_keys": [],
                 "namespace": "somenamespace",
                 "date_created": "1426500194",
@@ -2517,6 +2576,7 @@ index 0000000..60f7480
                     "name": "pingou",
                     "default_email": "pingou@fedoraproject.org",
                     "emails": ["pingou@fedoraproject.org"],
+                    "full_url": "http://localhost.localdomain/user/pingou",
                 },
                 "settings": {
                     "issue_tracker": True,
@@ -2532,6 +2592,7 @@ index 0000000..60f7480
                 "name": "pingou",
                 "default_email": "pingou@fedoraproject.org",
                 "emails": ["pingou@fedoraproject.org"],
+                "full_url": "http://localhost.localdomain/user/pingou",
             },
             "id": 5,
             "comments": [],
@@ -2542,6 +2603,7 @@ index 0000000..60f7480
                 "parent": {
                     "parent": None,
                     "name": "test",
+                    "full_url": "http://localhost.localdomain/test",
                     "custom_keys": [],
                     "date_created": "1426500194",
                     "tags": [],
@@ -2550,6 +2612,7 @@ index 0000000..60f7480
                         "name": "pingou",
                         "default_email": "pingou@fedoraproject.org",
                         "emails": ["pingou@fedoraproject.org"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                     "settings": {
                         "issue_tracker": True,
@@ -2565,6 +2628,7 @@ index 0000000..60f7480
                     "pull_requests": True,
                 },
                 "name": "test",
+                "full_url": "http://localhost.localdomain/test",
                 "date_created": "1426843440",
                 "custom_keys": [],
                 "tags": [],
@@ -2573,6 +2637,7 @@ index 0000000..60f7480
                     "name": "fake",
                     "default_email": "fake@fedoraproject.org",
                     "emails": ["fake@fedoraproject.org"],
+                    "full_url": "http://localhost.localdomain/user/pingou",
                 },
                 "project_docs": True,
                 "id": 6,
@@ -2602,7 +2667,7 @@ index 0000000..60f7480
         )
 
     def test_update_ticket_from_git_with_boards(self):
-        """ Test the update_ticket_from_git method from pagure.lib.git. """
+        """Test the update_ticket_from_git method from pagure.lib.git."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
 
@@ -2654,6 +2719,7 @@ index 0000000..60f7480
                             "tag_color": "DeepBlueSky",
                             "tag_description": "",
                         },
+                        "full_url": "http://localhost.localdomain/somenamespace/test3/boards/dev",
                     },
                     "rank": 2,
                     "status": {
@@ -2683,6 +2749,7 @@ index 0000000..60f7480
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                 }
             ],
@@ -2691,6 +2758,7 @@ index 0000000..60f7480
             "date_created": "1594654596",
             "depends": [],
             "id": 2,
+            "full_url": "http://localhost.localdomain/somenamespace/test3/issue/2",
             "last_updated": "1594654596",
             "milestone": None,
             "priority": None,
@@ -2705,6 +2773,7 @@ index 0000000..60f7480
                 "fullname": "foo bar",
                 "name": "foo",
                 "url_path": "user/foo",
+                "full_url": "http://localhost.localdomain/user/pingou",
             },
         }
 
@@ -2732,7 +2801,7 @@ index 0000000..60f7480
         self.assertEqual(len(namespaced_repo.boards), 1)
 
     def test_update_ticket_from_git_with_boards_twice(self):
-        """ Test the update_ticket_from_git method from pagure.lib.git. """
+        """Test the update_ticket_from_git method from pagure.lib.git."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
 
@@ -2792,6 +2861,7 @@ index 0000000..60f7480
                         "close_status": None,
                         "name": "In Progress",
                     },
+                    "full_url": "http://localhost.localdomain/somenamespace/test3/boards/dev",
                 }
             ],
             "close_status": None,
@@ -2813,6 +2883,7 @@ index 0000000..60f7480
                         "fullname": "PY C",
                         "name": "pingou",
                         "url_path": "user/pingou",
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                 }
             ],
@@ -2821,6 +2892,7 @@ index 0000000..60f7480
             "date_created": "1594654596",
             "depends": [],
             "id": 2,
+            "full_url": "http://localhost.localdomain/somenamespace/test3/issue/2",
             "last_updated": "1594654596",
             "milestone": None,
             "priority": None,
@@ -2835,6 +2907,7 @@ index 0000000..60f7480
                 "fullname": "foo bar",
                 "name": "foo",
                 "url_path": "user/foo",
+                "full_url": "http://localhost.localdomain/user/foo",
             },
         }
 
@@ -2890,7 +2963,7 @@ index 0000000..60f7480
         self.assertEqual(len(namespaced_repo.boards), 1)
 
     def test_update_request_from_git(self):
-        """ Test the update_request_from_git method from pagure.lib.git. """
+        """Test the update_request_from_git method from pagure.lib.git."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"))
 
@@ -2908,9 +2981,11 @@ index 0000000..60f7480
         data = {
             "status": True,
             "uid": "d4182a2ac2d541d884742d3037c26e57",
+            "full_url": "http://localhost.localdomain/test/pull-request/4",
             "project": {
                 "parent": None,
                 "name": "test",
+                "full_url": "http://localhost.localdomain/test",
                 "custom_keys": [],
                 "date_created": "1426500194",
                 "tags": [],
@@ -2919,6 +2994,7 @@ index 0000000..60f7480
                     "name": "pingou",
                     "default_email": "pingou@fedoraproject.org",
                     "emails": ["pingou@fedoraproject.org"],
+                    "full_url": "http://localhost.localdomain/user/pingou",
                 },
                 "settings": {
                     "issue_tracker": True,
@@ -2934,6 +3010,7 @@ index 0000000..60f7480
                 "name": "pingou",
                 "default_email": "pingou@fedoraproject.org",
                 "emails": ["pingou@fedoraproject.org"],
+                "full_url": "http://localhost.localdomain/user/pingou",
             },
             "id": 4,
             "comments": [],
@@ -2944,6 +3021,7 @@ index 0000000..60f7480
                 "parent": {
                     "parent": None,
                     "name": "test",
+                    "full_url": "http://localhost.localdomain/test",
                     "custom_keys": [],
                     "date_created": "1426500194",
                     "tags": [],
@@ -2952,6 +3030,7 @@ index 0000000..60f7480
                         "name": "pingou",
                         "default_email": "pingou@fedoraproject.org",
                         "emails": ["pingou@fedoraproject.org"],
+                        "full_url": "http://localhost.localdomain/user/pingou",
                     },
                     "settings": {
                         "issue_tracker": True,
@@ -2966,6 +3045,7 @@ index 0000000..60f7480
                     "project_documentation": True,
                     "pull_requests": True,
                 },
+                "full_url": "http://localhost.localdomain/fork/fake/test",
                 "name": "test",
                 "date_created": "1426843440",
                 "custom_keys": [],
@@ -2975,6 +3055,7 @@ index 0000000..60f7480
                     "name": "fake",
                     "default_email": "fake@fedoraproject.org",
                     "emails": ["fake@fedoraproject.org"],
+                    "full_url": "http://localhost.localdomain/user/fake",
                 },
                 "project_docs": True,
                 "id": 6,
@@ -3005,7 +3086,7 @@ index 0000000..60f7480
         self.assertEqual(repo.requests[0].tags_text, ["WIP", "core"])
 
     def test_read_git_lines(self):
-        """ Test the read_git_lines method of pagure.lib.git. """
+        """Test the read_git_lines method of pagure.lib.git."""
         self.test_update_git()
 
         gitrepo = os.path.join(
@@ -3026,7 +3107,7 @@ index 0000000..60f7480
         self.assertTrue(output[0].endswith(": Test issue'\n"))
 
     def test_get_revs_between(self):
-        """ Test the get_revs_between method of pagure.lib.git. """
+        """Test the get_revs_between method of pagure.lib.git."""
 
         self.test_update_git()
 
@@ -3098,7 +3179,7 @@ index 0000000..60f7480
         self.assertEqual(output4, [branch_commit.oid.hex])
 
     def test_get_author(self):
-        """ Test the get_author method of pagure.lib.git. """
+        """Test the get_author method of pagure.lib.git."""
 
         self.test_update_git()
 
@@ -3115,7 +3196,7 @@ index 0000000..60f7480
             self.assertEqual(output, "pagure")
 
     def get_author_email(self):
-        """ Test the get_author_email method of pagure.lib.git. """
+        """Test the get_author_email method of pagure.lib.git."""
 
         self.test_update_git()
 
@@ -3130,7 +3211,7 @@ index 0000000..60f7480
             self.assertEqual(output, "pagure")
 
     def test_get_repo_name(self):
-        """ Test the get_repo_name method of pagure.lib.git. """
+        """Test the get_repo_name method of pagure.lib.git."""
 
         def runtest(reponame, *path):
             gitrepo = os.path.join(self.path, "repos", *path)
@@ -3143,7 +3224,7 @@ index 0000000..60f7480
         runtest("foo.test", "foo.test.git")
 
     def test_get_username(self):
-        """ Test the get_username method of pagure.lib.git. """
+        """Test the get_username method of pagure.lib.git."""
 
         def runtest(username, *path):
             gitrepo = os.path.join(self.path, "repos", *path)
@@ -3158,7 +3239,7 @@ index 0000000..60f7480
         runtest("pingou", "forks", "pingou", "bar/foo.test.git")
 
     def test_get_repo_namespace(self):
-        """ Test the get_repo_namespace method of pagure.lib.git. """
+        """Test the get_repo_namespace method of pagure.lib.git."""
 
         def runtest(namespace, *path):
             gitrepo = os.path.join(self.path, "repos", *path)
@@ -3176,7 +3257,7 @@ index 0000000..60f7480
         runtest("bar", "forks", "user", "bar", "foo.test.git")
 
     def test_update_custom_fields_from_json(self):
-        """ Test the update_custom_fields_from_json method of lib.git """
+        """Test the update_custom_fields_from_json method of lib.git"""
 
         tests.create_projects(self.session)
         repo = pagure.lib.query._get_project(self.session, "test")
@@ -3274,8 +3355,8 @@ index 0000000..60f7480
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.lib.git.update_git")
     def test_merge_pull_request_no_master(self, email_f, up_git):
-        """ Test the merge_pull_request function when there are no master
-        branch in the repo. """
+        """Test the merge_pull_request function when there are no master
+        branch in the repo."""
         email_f.return_value = True
         up_git.return_value = True
 
@@ -3355,8 +3436,8 @@ index 0000000..60f7480
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.lib.git.update_git")
     def test_merge_pull_request_closed(self, email_f, up_git):
-        """ Test the merge_pull_request function when the PR was already
-        closed/merged. """
+        """Test the merge_pull_request function when the PR was already
+        closed/merged."""
         email_f.return_value = True
         up_git.return_value = True
 
@@ -3445,7 +3526,7 @@ index 0000000..60f7480
 
     @patch("subprocess.Popen")
     def test_generate_gitolite_acls(self, popen):
-        """ Test calling generate_gitolite_acls. """
+        """Test calling generate_gitolite_acls."""
         pagure.SESSION = self.session
         pagure.lib.git.SESSION = self.session
         pagure.config.config["GITOLITE_HOME"] = "/tmp"
@@ -3530,12 +3611,12 @@ index 0000000..60f7480
 
 
 class PagureLibGitCommitToPatchtests(tests.Modeltests):
-    """ Tests for pagure.lib.git """
+    """Tests for pagure.lib.git"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environment for the tests. """
+        """Set up the environment for the tests."""
         super(PagureLibGitCommitToPatchtests, self).setUp()
 
         # Create a git repo to play with
@@ -3605,7 +3686,7 @@ class PagureLibGitCommitToPatchtests(tests.Modeltests):
         self.third_commit = repo.revparse_single("HEAD")
 
     def test_commit_to_patch_first_commit(self):
-        """ Test the commit_to_patch function of pagure.lib.git. """
+        """Test the commit_to_patch function of pagure.lib.git."""
         repo = pygit2.init_repository(self.gitrepo)
 
         patch = pagure.lib.git.commit_to_patch(repo, self.first_commit)
@@ -3639,7 +3720,7 @@ index 0000000..9f44358
         self.assertEqual(patch, exp)
 
     def test_commit_to_patch_single_commit(self):
-        """ Test the commit_to_patch function of pagure.lib.git. """
+        """Test the commit_to_patch function of pagure.lib.git."""
         repo = pygit2.init_repository(self.gitrepo)
 
         patch = pagure.lib.git.commit_to_patch(repo, self.second_commit)
@@ -3677,7 +3758,7 @@ index 9f44358..2a552bb 100644
         self.assertEqual(patch, exp)
 
     def test_commit_to_patch_2_commits(self):
-        """ Test the commit_to_patch function of pagure.lib.git. """
+        """Test the commit_to_patch function of pagure.lib.git."""
         repo = pygit2.init_repository(self.gitrepo)
 
         patch = pagure.lib.git.commit_to_patch(
@@ -3734,7 +3815,7 @@ index 9f44358..2a552bb 100644
         self.assertEqual(patch, exp)
 
     def test_commit_to_patch_first_commit_diff(self):
-        """ Test the commit_to_patch function of pagure.lib.git. """
+        """Test the commit_to_patch function of pagure.lib.git."""
         repo = pygit2.init_repository(self.gitrepo)
 
         patch = pagure.lib.git.commit_to_patch(
@@ -3762,7 +3843,7 @@ index 0000000..9f44358
         self.assertEqual(patch, exp)
 
     def test_commit_to_patch_single_commit_diff(self):
-        """ Test the commit_to_patch function of pagure.lib.git. """
+        """Test the commit_to_patch function of pagure.lib.git."""
         repo = pygit2.init_repository(self.gitrepo)
 
         patch = pagure.lib.git.commit_to_patch(
@@ -3793,7 +3874,7 @@ index 9f44358..2a552bb 100644
         self.assertEqual(patch, exp)
 
     def test_commit_to_patch_two_commits_diff(self):
-        """ Test the commit_to_patch function of pagure.lib.git. """
+        """Test the commit_to_patch function of pagure.lib.git."""
         repo = pygit2.init_repository(self.gitrepo)
 
         patch = pagure.lib.git.commit_to_patch(
@@ -3833,7 +3914,7 @@ index 9f44358..2a552bb 100644
         self.assertEqual(patch, exp)
 
     def test_commit_to_patch_first_commit_diff_separated(self):
-        """ Test the commit_to_patch function of pagure.lib.git. """
+        """Test the commit_to_patch function of pagure.lib.git."""
         repo = pygit2.init_repository(self.gitrepo)
 
         patches = pagure.lib.git.commit_to_patch(
@@ -3865,7 +3946,7 @@ index 0000000..9f44358
         self.assertEqual(output, [exp])
 
     def test_commit_to_patch_single_commit_diff_separated(self):
-        """ Test the commit_to_patch function of pagure.lib.git. """
+        """Test the commit_to_patch function of pagure.lib.git."""
         repo = pygit2.init_repository(self.gitrepo)
 
         patches = pagure.lib.git.commit_to_patch(
@@ -3900,7 +3981,7 @@ index 9f44358..2a552bb 100644
         self.assertEqual(output, [exp])
 
     def test_commit_to_patch_two_commits_diff_separated(self):
-        """ Test the commit_to_patch function of pagure.lib.git. """
+        """Test the commit_to_patch function of pagure.lib.git."""
         repo = pygit2.init_repository(self.gitrepo)
 
         patches = pagure.lib.git.commit_to_patch(
@@ -3946,11 +4027,10 @@ index 9f44358..2a552bb 100644
 
             patch = "\n".join(npatch)
             output.append(patch)
-
         self.assertEqual(output, exp)
 
     def test_commit_to_patch_empty_commit(self):
-        """ Test the commit_to_path function of pagure.lib.git. """
+        """Test the commit_to_path function of pagure.lib.git."""
         repo = pygit2.init_repository(self.gitrepo)
 
         patch = pagure.lib.git.commit_to_patch(repo, self.third_commit)
@@ -3958,7 +4038,7 @@ index 9f44358..2a552bb 100644
         self.assertEqual(patch, exp)
 
     def test_commit_to_patch_empty_commit_diff(self):
-        """ Test the commit_to_patch function of pagure.lib.git. """
+        """Test the commit_to_patch function of pagure.lib.git."""
         repo = pygit2.init_repository(self.gitrepo)
 
         patch = pagure.lib.git.commit_to_patch(
diff --git a/tests/test_pagure_lib_git_auth.py b/tests/test_pagure_lib_git_auth.py
index 4a22754..cc1bfa8 100644
--- a/tests/test_pagure_lib_git_auth.py
+++ b/tests/test_pagure_lib_git_auth.py
@@ -35,7 +35,7 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureLibGitAuthtests(tests.Modeltests):
-    """ Tests for pagure.lib.git_auth """
+    """Tests for pagure.lib.git_auth"""
 
     config_values = {"authbackend": "test_auth"}
 
@@ -233,7 +233,7 @@ class PagureLibGitAuthtests(tests.Modeltests):
 
 
 class PagureLibGitAuthPagureBackendtests(tests.Modeltests):
-    """ Tests for pagure.lib.git_auth """
+    """Tests for pagure.lib.git_auth"""
 
     config_values = {"authbackend": "pagure"}
 
diff --git a/tests/test_pagure_lib_git_auth_paguregitauth.py b/tests/test_pagure_lib_git_auth_paguregitauth.py
index f7dbf28..1d020e6 100644
--- a/tests/test_pagure_lib_git_auth_paguregitauth.py
+++ b/tests/test_pagure_lib_git_auth_paguregitauth.py
@@ -29,7 +29,7 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureLibGitAuthPagureGitAuthtests(tests.Modeltests):
-    """ Tests for pagure.lib.git_auth PagureGitAuth dynamic ACL """
+    """Tests for pagure.lib.git_auth PagureGitAuth dynamic ACL"""
 
     config_values = {"authbackend": "pagure"}
 
diff --git a/tests/test_pagure_lib_git_diff_pr.py b/tests/test_pagure_lib_git_diff_pr.py
index 612ecdf..b677338 100644
--- a/tests/test_pagure_lib_git_diff_pr.py
+++ b/tests/test_pagure_lib_git_diff_pr.py
@@ -19,7 +19,9 @@ import time  # noqa
 import os  # noqa
 
 import pygit2  # noqa
-from mock import patch, MagicMock  # noqa
+import pagure_messages
+from fedora_messaging import api, testing
+from mock import ANY, patch, MagicMock
 
 sys.path.insert(
     0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
@@ -31,11 +33,11 @@ from pagure.lib.repo import PagureRepo  # noqa
 
 
 class PagureFlaskForkPrtests(tests.Modeltests):
-    """ Tests for flask fork controller of pagure regarding diffing PRs """
+    """Tests for flask fork controller of pagure regarding diffing PRs"""
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureFlaskForkPrtests, self).setUp()
 
         # Create the main project in the DB
@@ -228,7 +230,7 @@ class PagureFlaskForkPrtests(tests.Modeltests):
         self.assertEqual(req.title, "test pull-request")
 
     def test_get_pr_info(self):
-        """ Test pagure.ui.fork._get_pr_info """
+        """Test pagure.ui.fork._get_pr_info"""
 
         gitrepo = os.path.join(self.path, "repos", "test.git")
         gitrepo2 = os.path.join(
@@ -255,7 +257,7 @@ class PagureFlaskForkPrtests(tests.Modeltests):
         )
 
     def test_get_pr_info_raises(self):
-        """ Test pagure.ui.fork._get_pr_info """
+        """Test pagure.ui.fork._get_pr_info"""
 
         gitrepo = os.path.join(self.path, "repos", "test.git")
         gitrepo2 = os.path.join(
@@ -281,7 +283,7 @@ class PagureFlaskForkPrtests(tests.Modeltests):
         )
 
     def test_diff_pull_request(self):
-        """ Test pagure.lib.git.diff_pull_request """
+        """Test pagure.lib.git.diff_pull_request"""
         gitrepo = os.path.join(self.path, "repos", "test.git")
         gitrepo2 = os.path.join(
             self.path, "repos", "forks", "pingou", "test.git"
@@ -320,8 +322,11 @@ class PagureFlaskForkPrtests(tests.Modeltests):
         commit = pr_ref.peel()
         self.assertEqual(commit.oid.hex, diff_commits[0].oid.hex)
 
+    @patch.dict(
+        "pagure.config.config", {"FEDORA_MESSAGING_NOTIFICATIONS": True}
+    )
     def test_diff_pull_request_updated(self):
-        """ Test that calling pagure.lib.git.diff_pull_request on an updated
+        """Test that calling pagure.lib.git.diff_pull_request on an updated
         PR updates the PR reference
         """
         gitrepo = os.path.join(self.path, "repos", "test.git")
@@ -391,14 +396,353 @@ class PagureFlaskForkPrtests(tests.Modeltests):
 
         # Get the new diff for that PR and check its new ref
 
-        diff_commits, diff = pagure.lib.git.diff_pull_request(
-            self.session,
-            request=request,
-            repo_obj=PagureRepo(gitrepo2),
-            orig_repo=PagureRepo(gitrepo),
-            with_diff=True,
-        )
-        self.assertEqual(len(diff_commits), 3)
+        with testing.mock_sends(
+            pagure_messages.PullRequestUpdatedV1(
+                topic="pagure.pull-request.updated",
+                body={
+                    "pullrequest": {
+                        "id": 1,
+                        "uid": ANY,
+                        "full_url": "http://localhost.localdomain/test/pull-request/1",
+                        "title": "test pull-request",
+                        "branch": "master",
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "fullname": "test",
+                            "url_path": "test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "branch_from": "feature_foo",
+                        "repo_from": {
+                            "id": 2,
+                            "name": "test",
+                            "fullname": "forks/pingou/test",
+                            "url_path": "fork/pingou/test",
+                            "full_url": "http://localhost.localdomain/fork/pingou/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": {
+                                "id": 1,
+                                "name": "test",
+                                "fullname": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "url_path": "test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "remote_git": None,
+                        "date_created": ANY,
+                        "updated_on": ANY,
+                        "last_updated": ANY,
+                        "closed_at": None,
+                        "user": {
+                            "name": "pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                        },
+                        "assignee": None,
+                        "status": "Open",
+                        "commit_start": ANY,
+                        "commit_stop": ANY,
+                        "closed_by": None,
+                        "initial_comment": None,
+                        "cached_merge_status": "unknown",
+                        "threshold_reached": None,
+                        "tags": [],
+                        "comments": [],
+                    },
+                    "agent": "pagure",
+                },
+            ),
+            pagure_messages.PullRequestCommentAddedV1(
+                topic="pagure.pull-request.comment.added",
+                body={
+                    "pullrequest": {
+                        "id": 1,
+                        "full_url": "http://localhost.localdomain/test/pull-request/1",
+                        "uid": ANY,
+                        "title": "test pull-request",
+                        "branch": "master",
+                        "project": {
+                            "id": 1,
+                            "name": "test",
+                            "fullname": "test",
+                            "full_url": "http://localhost.localdomain/test",
+                            "url_path": "test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": None,
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "branch_from": "feature_foo",
+                        "repo_from": {
+                            "id": 2,
+                            "name": "test",
+                            "full_url": "http://localhost.localdomain/fork/pingou/test",
+                            "fullname": "forks/pingou/test",
+                            "url_path": "fork/pingou/test",
+                            "description": "test project #1",
+                            "namespace": None,
+                            "parent": {
+                                "id": 1,
+                                "name": "test",
+                                "full_url": "http://localhost.localdomain/test",
+                                "fullname": "test",
+                                "url_path": "test",
+                                "description": "test project #1",
+                                "namespace": None,
+                                "parent": None,
+                                "date_created": ANY,
+                                "date_modified": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "access_users": {
+                                    "owner": ["pingou"],
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "access_groups": {
+                                    "admin": [],
+                                    "commit": [],
+                                    "collaborator": [],
+                                    "ticket": [],
+                                },
+                                "tags": [],
+                                "priorities": {},
+                                "custom_keys": [],
+                                "close_status": [
+                                    "Invalid",
+                                    "Insufficient data",
+                                    "Fixed",
+                                    "Duplicate",
+                                ],
+                                "milestones": {},
+                            },
+                            "date_created": ANY,
+                            "date_modified": ANY,
+                            "user": {
+                                "name": "pingou",
+                                "fullname": "PY C",
+                                "url_path": "user/pingou",
+                                "full_url": "http://localhost.localdomain/user/pingou",
+                            },
+                            "access_users": {
+                                "owner": ["pingou"],
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "access_groups": {
+                                "admin": [],
+                                "commit": [],
+                                "collaborator": [],
+                                "ticket": [],
+                            },
+                            "tags": [],
+                            "priorities": {},
+                            "custom_keys": [],
+                            "close_status": [
+                                "Invalid",
+                                "Insufficient data",
+                                "Fixed",
+                                "Duplicate",
+                            ],
+                            "milestones": {},
+                        },
+                        "remote_git": None,
+                        "date_created": ANY,
+                        "updated_on": ANY,
+                        "last_updated": ANY,
+                        "closed_at": None,
+                        "user": {
+                            "name": "pingou",
+                            "fullname": "PY C",
+                            "url_path": "user/pingou",
+                            "full_url": "http://localhost.localdomain/user/pingou",
+                        },
+                        "assignee": None,
+                        "status": "Open",
+                        "commit_start": ANY,
+                        "commit_stop": ANY,
+                        "closed_by": None,
+                        "initial_comment": None,
+                        "cached_merge_status": "unknown",
+                        "threshold_reached": None,
+                        "tags": [],
+                        "comments": [
+                            {
+                                "id": 1,
+                                "commit": None,
+                                "tree": None,
+                                "filename": None,
+                                "line": None,
+                                "comment": "**1 new commit added**\n\n "
+                                "* ``Third edit on side branch of the file "
+                                "sources for testing``\n",
+                                "parent": None,
+                                "date_created": ANY,
+                                "user": {
+                                    "name": "pingou",
+                                    "fullname": "PY C",
+                                    "url_path": "user/pingou",
+                                    "full_url": "http://localhost.localdomain/user/pingou",
+                                },
+                                "edited_on": None,
+                                "editor": None,
+                                "notification": True,
+                                "reactions": {},
+                            }
+                        ],
+                    },
+                    "agent": "pingou",
+                },
+            ),
+        ):
+            diff_commits, diff = pagure.lib.git.diff_pull_request(
+                self.session,
+                request=request,
+                repo_obj=PagureRepo(gitrepo2),
+                orig_repo=PagureRepo(gitrepo),
+                with_diff=True,
+            )
+            self.assertEqual(len(diff_commits), 3)
 
         # Check that the PR has its PR refs
         # we don't know the task id but we'll give it 30 sec to finish
@@ -414,7 +758,7 @@ class PagureFlaskForkPrtests(tests.Modeltests):
         self.assertNotEqual(commit.oid.hex, commit2.oid.hex)
 
     def test_two_diff_pull_request_sequentially(self):
-        """ Test calling pagure.lib.git.diff_pull_request twice returns
+        """Test calling pagure.lib.git.diff_pull_request twice returns
         the same data
         """
         gitrepo = os.path.join(self.path, "repos", "test.git")
diff --git a/tests/test_pagure_lib_git_get_tags_objects.py b/tests/test_pagure_lib_git_get_tags_objects.py
index f9b3325..555b700 100644
--- a/tests/test_pagure_lib_git_get_tags_objects.py
+++ b/tests/test_pagure_lib_git_get_tags_objects.py
@@ -27,7 +27,7 @@ import tests
 
 
 def get_tag_name(tags):
-    """ Return a list of the tag names """
+    """Return a list of the tag names"""
     output = []
     for tag in tags:
         output.append(tag["tagname"])
@@ -35,7 +35,7 @@ def get_tag_name(tags):
 
 
 def add_repo_tag(git_dir, repo, tags, repo_name):
-    """ Use a list to create multiple tags on a git repo """
+    """Use a list to create multiple tags on a git repo"""
     for tag in reversed(tags):
         time.sleep(1)
         tests.add_commit_git_repo(
@@ -54,7 +54,7 @@ def add_repo_tag(git_dir, repo, tags, repo_name):
 
 class PagureLibGitGetTagstests(tests.Modeltests):
     def test_get_git_tags_objects(self):
-        """ Test the get_git_tags_objects method of pagure.lib.git. """
+        """Test the get_git_tags_objects method of pagure.lib.git."""
         tests.create_projects(self.session)
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
         project = pagure.lib.query._get_project(self.session, "test")
diff --git a/tests/test_pagure_lib_git_mirror_project.py b/tests/test_pagure_lib_git_mirror_project.py
index 4773a6a..90abc01 100644
--- a/tests/test_pagure_lib_git_mirror_project.py
+++ b/tests/test_pagure_lib_git_mirror_project.py
@@ -33,12 +33,12 @@ from pagure.lib.repo import PagureRepo
 
 
 class PagureLibGitMirrorProjecttests(tests.Modeltests):
-    """ Tests for pagure.lib.git.mirror_pull_project """
+    """Tests for pagure.lib.git.mirror_pull_project"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureLibGitMirrorProjecttests, self).setUp()
 
         tests.create_projects(self.session)
@@ -55,7 +55,7 @@ class PagureLibGitMirrorProjecttests(tests.Modeltests):
     @patch("subprocess.Popen")
     @patch("subprocess.check_output")
     def test_mirror_pull_project(self, ck_out_mock, popen_mock):
-        """ Test the mirror_pull_project method of pagure.lib.git. """
+        """Test the mirror_pull_project method of pagure.lib.git."""
 
         tmp = MagicMock()
         tmp.communicate.return_value = ("", "")
diff --git a/tests/test_pagure_lib_gitolite_config.py b/tests/test_pagure_lib_gitolite_config.py
index d080612..181df88 100644
--- a/tests/test_pagure_lib_gitolite_config.py
+++ b/tests/test_pagure_lib_gitolite_config.py
@@ -76,12 +76,12 @@ repo requests/somenamespace/test3
 
 
 class PagureLibGitoliteConfigtests(tests.Modeltests):
-    """ Tests for pagure.lib.git """
+    """Tests for pagure.lib.git"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureLibGitoliteConfigtests, self).setUp()
 
         tests.create_projects(self.session)
@@ -102,7 +102,7 @@ class PagureLibGitoliteConfigtests(tests.Modeltests):
             stream.write("\n# end of footer\n")
 
     def tearDown(self):
-        """ Tearn down the environnment, ran before every tests. """
+        """Tearn down the environnment, ran before every tests."""
         super(PagureLibGitoliteConfigtests, self).tearDown()
 
         if os.path.exists(self.outputconf):
@@ -110,8 +110,8 @@ class PagureLibGitoliteConfigtests(tests.Modeltests):
         self.assertFalse(os.path.exists(self.outputconf))
 
     def test_write_gitolite_pre_post_projectNone(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git with
-        a postconf set """
+        """Test the write_gitolite_acls function of pagure.lib.git with
+        a postconf set"""
 
         helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
         helper.write_gitolite_acls(
@@ -147,8 +147,8 @@ class PagureLibGitoliteConfigtests(tests.Modeltests):
         self.assertEqual(data, exp)
 
     def test_write_gitolite_pre_post_projectNone_to_existing_file(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git with
-        a postconf set with existing output file """
+        """Test the write_gitolite_acls function of pagure.lib.git with
+        a postconf set with existing output file"""
 
         with open(self.outputconf, "w") as stream:
             pass
@@ -168,8 +168,8 @@ class PagureLibGitoliteConfigtests(tests.Modeltests):
         self.assertEqual(data, "")
 
     def test_write_gitolite_pre_post_project_1(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git with
-        a postconf set """
+        """Test the write_gitolite_acls function of pagure.lib.git with
+        a postconf set"""
 
         with open(self.outputconf, "w") as stream:
             pass
@@ -209,8 +209,8 @@ class PagureLibGitoliteConfigtests(tests.Modeltests):
         self.assertEqual(data, exp)
 
     def test_write_gitolite_pre_post_project_test(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git with
-        a postconf set """
+        """Test the write_gitolite_acls function of pagure.lib.git with
+        a postconf set"""
 
         with open(self.outputconf, "w") as stream:
             pass
@@ -260,8 +260,8 @@ repo requests/test
         self.assertEqual(data, exp)
 
     def test_write_gitolite_pre_post_project_test_full_file(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git with
-        a postconf set """
+        """Test the write_gitolite_acls function of pagure.lib.git with
+        a postconf set"""
 
         # Re-generate the gitolite config for all the projects
         self.test_write_gitolite_pre_post_project_1()
@@ -362,8 +362,8 @@ repo requests/test
         "pagure.config.config", {"ENABLE_DOCS": False, "ENABLE_TICKETS": False}
     )
     def test_write_gitolite_disabled_docs_tickets(self):
-        """ Test the write_gitolite_acls function when docs and tickets
-        are disabled """
+        """Test the write_gitolite_acls function when docs and tickets
+        are disabled"""
 
         # Re-generate the gitolite config for all the projects
         project = pagure.lib.query._get_project(self.session, "test")
@@ -421,7 +421,7 @@ repo requests/somenamespace/test3
 
 
 class PagureLibGitoliteGroupConfigtests(tests.Modeltests):
-    """ Tests for generating the gitolite configuration file for a group
+    """Tests for generating the gitolite configuration file for a group
     change
 
     """
@@ -429,7 +429,7 @@ class PagureLibGitoliteGroupConfigtests(tests.Modeltests):
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureLibGitoliteGroupConfigtests, self).setUp()
 
         tests.create_projects(self.session)
@@ -472,7 +472,7 @@ class PagureLibGitoliteGroupConfigtests(tests.Modeltests):
             stream.write("\n# end of footer\n")
 
     def tearDown(self):
-        """ Tearn down the environnment, ran before every tests. """
+        """Tearn down the environnment, ran before every tests."""
         super(PagureLibGitoliteGroupConfigtests, self).tearDown()
 
         if os.path.exists(self.outputconf):
@@ -480,7 +480,7 @@ class PagureLibGitoliteGroupConfigtests(tests.Modeltests):
         self.assertFalse(os.path.exists(self.outputconf))
 
     def test_write_gitolite_project_test_group(self):
-        """ Test the write_gitolite_acls when updating a single group. """
+        """Test the write_gitolite_acls when updating a single group."""
 
         with open(self.outputconf, "w") as stream:
             pass
@@ -534,7 +534,7 @@ repo requests/test
         self.assertEqual(data, exp)
 
     def test_write_gitolite_project_test_all_groups(self):
-        """ Test the write_gitolite_acls when updating all groups. """
+        """Test the write_gitolite_acls when updating all groups."""
 
         with open(self.outputconf, "w") as stream:
             pass
@@ -588,7 +588,7 @@ repo requests/test
         self.assertEqual(data, exp)
 
     def test_write_gitolite_project_all_projects_groups(self):
-        """ Test the generating the entire gitolite config. """
+        """Test the generating the entire gitolite config."""
 
         with open(self.outputconf, "w") as stream:
             pass
@@ -631,7 +631,7 @@ repo requests/test
         self.assertEqual(data, exp)
 
     def test_write_gitolite_project_all_projects_one_group(self):
-        """ Test the generating the entire gitolite config. """
+        """Test the generating the entire gitolite config."""
 
         # Generate the full gitolite config that we will update
         self.test_write_gitolite_project_all_projects_groups()
@@ -741,7 +741,7 @@ repo requests/test
         self.assertEqual(data, exp)
 
     def test_write_gitolite_delete_group(self):
-        """ Test the updating the gitolite config after having
+        """Test the updating the gitolite config after having
         deleted a group.
         """
 
@@ -793,8 +793,8 @@ repo requests/test
 
     @patch("pagure.lib.git_auth.get_git_auth_helper")
     def test_task_generate_gitolite_acls_one_group(self, get_helper):
-        """ Test the generate_gitolite_acls task to ensure if group is None
-        then None is passed to the helper. """
+        """Test the generate_gitolite_acls task to ensure if group is None
+        then None is passed to the helper."""
         helper = MagicMock()
         get_helper.return_value = helper
         pagure.lib.query.SESSIONMAKER = self.session.session_factory
@@ -809,8 +809,8 @@ repo requests/test
         self.assertIsNotNone(args[1].get("project"))
 
     def test_write_gitolite_project_test_private(self):
-        """ Test the write_gitolite_acls function of pagure.lib.git with
-        a postconf set """
+        """Test the write_gitolite_acls function of pagure.lib.git with
+        a postconf set"""
 
         # Make the test project private
         project = pagure.lib.query._get_project(self.session, "test")
@@ -876,8 +876,8 @@ repo requests/somenamespace/test3
         self.assertEqual(data, exp)
 
     def test_remove_acls(self):
-        """ Test the remove_acls function of pagure.lib.git when deleting
-        a project """
+        """Test the remove_acls function of pagure.lib.git when deleting
+        a project"""
         pagure.config.config["GITOLITE_CONFIG"] = self.outputconf
 
         with open(self.outputconf, "w") as stream:
@@ -954,8 +954,8 @@ repo requests/somenamespace/test3
         self.assertEqual(data, exp)
 
     def test_remove_acls_no_project(self):
-        """ Test the remove_acls function of pagure.lib.git when no project
-        is specified """
+        """Test the remove_acls function of pagure.lib.git when no project
+        is specified"""
         pagure.config.config["GITOLITE_CONFIG"] = self.outputconf
 
         with open(self.outputconf, "w") as stream:
diff --git a/tests/test_pagure_lib_link.py b/tests/test_pagure_lib_link.py
index eb4b192..d4907bc 100644
--- a/tests/test_pagure_lib_link.py
+++ b/tests/test_pagure_lib_link.py
@@ -38,11 +38,10 @@ COMMENTS = [
 
 
 class PagureLibLinktests(tests.Modeltests):
-    """ Tests for pagure.lib.link """
+    """Tests for pagure.lib.link"""
 
     def test_get_relation_relates(self):
-        """ Test the get_relation function of pagure.lib.link with relates.
-        """
+        """Test the get_relation function of pagure.lib.link with relates."""
 
         link = pagure.lib.link.get_relation(
             self.session,
@@ -142,8 +141,7 @@ class PagureLibLinktests(tests.Modeltests):
                 self.assertEqual(link, [])
 
     def test_get_relation_fixes(self):
-        """ Test the get_relation function of pagure.lib.link with fixes.
-        """
+        """Test the get_relation function of pagure.lib.link with fixes."""
 
         link = pagure.lib.link.get_relation(
             self.session,
@@ -200,7 +198,7 @@ class PagureLibLinktests(tests.Modeltests):
                 self.assertEqual(link, [])
 
     def test_relates_regex(self):
-        """ Test the relates regex present in pagure.lib.link. """
+        """Test the relates regex present in pagure.lib.link."""
         text = "relates  to   http://localhost.localdomain/fork/pingou/test/issue/1"
         for index, regex in enumerate(pagure.lib.link.RELATES):
             if index == 1:
@@ -240,7 +238,7 @@ class PagureLibLinktests(tests.Modeltests):
                 self.assertEqual(regex.match(text), None)
 
     def test_fixes_regex(self):
-        """ Test the fixes regex present in pagure.lib.link. """
+        """Test the fixes regex present in pagure.lib.link."""
 
         # project/issue matches
         def project_match(text, groups):
diff --git a/tests/test_pagure_lib_login.py b/tests/test_pagure_lib_login.py
index d6c7213..ee4ec23 100644
--- a/tests/test_pagure_lib_login.py
+++ b/tests/test_pagure_lib_login.py
@@ -30,28 +30,28 @@ import tests
 
 
 class PagureLibLogintests(tests.Modeltests):
-    """ Tests for pagure.lib.login """
+    """Tests for pagure.lib.login"""
 
     def test_id_generator(self):
-        """ Test pagure.lib.login.id_generator. """
+        """Test pagure.lib.login.id_generator."""
         self.assertEqual(
             pagure.lib.login.id_generator(size=3, chars=["a"]), "aaa"
         )
 
     def test_get_session_by_visitkey(self):
-        """ Test pagure.lib.login.get_session_by_visitkey. """
+        """Test pagure.lib.login.get_session_by_visitkey."""
 
         session = pagure.lib.login.get_session_by_visitkey(self.session, "foo")
         self.assertEqual(session, None)
 
     def test_generate_hashed_value(self):
-        """ Test pagure.lib.login.generate_hashed_value. """
+        """Test pagure.lib.login.generate_hashed_value."""
         password = pagure.lib.login.generate_hashed_value("foo")
         self.assertTrue(password.startswith("$2$"))
         self.assertEqual(len(password), 63)
 
     def test_check_password(self):
-        """ Test pagure.lib.login.check_password. """
+        """Test pagure.lib.login.check_password."""
 
         # Version 2
         password = pagure.lib.login.generate_hashed_value("foo")
@@ -107,8 +107,7 @@ class PagureLibLogintests(tests.Modeltests):
         )
 
     def test_unicode_required(self):
-        """ Test to check for non-ascii password
-        """
+        """Test to check for non-ascii password"""
         self.assertRaises(
             ValueError,
             pagure.lib.login.generate_hashed_value,
diff --git a/tests/test_pagure_lib_mimetype.py b/tests/test_pagure_lib_mimetype.py
index d5947be..11b079d 100644
--- a/tests/test_pagure_lib_mimetype.py
+++ b/tests/test_pagure_lib_mimetype.py
@@ -9,6 +9,12 @@ import os
 import unittest
 import sys
 
+cchardet = None
+try:
+    import cchardet
+except ImportError:
+    pass
+
 from pagure.lib import mimetype
 
 sys.path.insert(
@@ -20,8 +26,18 @@ class TestMIMEType(unittest.TestCase):
     def test_guess_type(self):
         dataset = [
             ("hello.html", None, "text/html", None),
-            ("hello.html", b"#!", "text/html", "ascii"),
-            ("hello", b"#!", "text/plain", "ascii"),
+            (
+                "hello.html",
+                b"#!",
+                "text/html",
+                "ascii" if cchardet is None else "ASCII",
+            ),
+            (
+                "hello",
+                b"#!",
+                "text/plain",
+                "ascii" if cchardet is None else "ASCII",
+            ),
             ("hello.jpg", None, "image/jpeg", None),
             ("hello.jpg", b"#!", "image/jpeg", None),
             ("hello.jpg", b"\0", "image/jpeg", None),
@@ -49,7 +65,13 @@ class TestMIMEType(unittest.TestCase):
 
     def test_get_normal_headers(self):
         dataset = [
-            ("hello", b"#!", "text/plain; charset=ascii"),
+            (
+                "hello",
+                b"#!",
+                "text/plain; charset=ascii"
+                if cchardet is None
+                else "text/plain; charset=ASCII",
+            ),
             ("hello.jpg", None, "image/jpeg"),
             ("hello.jpg", b"#!", "image/jpeg"),
             ("hello.jpg", b"\0", "image/jpeg"),
diff --git a/tests/test_pagure_lib_model.py b/tests/test_pagure_lib_model.py
index 6536064..cf27498 100644
--- a/tests/test_pagure_lib_model.py
+++ b/tests/test_pagure_lib_model.py
@@ -26,10 +26,10 @@ import tests
 
 
 class PagureLibModeltests(tests.Modeltests):
-    """ Tests for pagure.lib.model """
+    """Tests for pagure.lib.model"""
 
     def test_user__repr__(self):
-        """ Test the User.__repr__ function of pagure.lib.model. """
+        """Test the User.__repr__ function of pagure.lib.model."""
         item = pagure.lib.query.search_user(self.session, email="foo@bar.com")
         self.assertEqual(str(item), "User: 2 - name foo")
         self.assertEqual("foo", item.user)
@@ -39,7 +39,7 @@ class PagureLibModeltests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_issue__repr__(self, p_send_email, p_ugt):
-        """ Test the Issue.__repr__ function of pagure.lib.model. """
+        """Test the Issue.__repr__ function of pagure.lib.model."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -66,7 +66,7 @@ class PagureLibModeltests(tests.Modeltests):
     @patch("pagure.lib.git.update_git")
     @patch("pagure.lib.notify.send_email")
     def test_pullrequest__repr__(self, p_send_email, p_ugt):
-        """ Test the PullRequest.__repr__ function of pagure.lib.model. """
+        """Test the PullRequest.__repr__ function of pagure.lib.model."""
         p_send_email.return_value = True
         p_ugt.return_value = True
 
@@ -117,7 +117,7 @@ class PagureLibModeltests(tests.Modeltests):
         )
 
     def test_paguregroup__repr__(self):
-        """ Test the PagureGroup.__repr__ function of pagure.lib.model. """
+        """Test the PagureGroup.__repr__ function of pagure.lib.model."""
         item = pagure.lib.model.PagureGroup(
             group_name="admin",
             display_name="admin group",
@@ -130,7 +130,7 @@ class PagureLibModeltests(tests.Modeltests):
         self.assertEqual(str(item), "Group: 1 - name admin")
 
     def test_tagissue__repr__(self):
-        """ Test the TagIssue.__repr__ function of pagure.lib.model. """
+        """Test the TagIssue.__repr__ function of pagure.lib.model."""
         self.test_issue__repr__()
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         issues = pagure.lib.query.search_issues(self.session, repo)
@@ -146,7 +146,7 @@ class PagureLibModeltests(tests.Modeltests):
         self.assertEqual(str(item), "TagIssue(issue:1, tag:foo)")
 
     def test_tagissuecolor__repr__(self):
-        """ Test the TagIssue.__repr__ function of pagure.lib.model. """
+        """Test the TagIssue.__repr__ function of pagure.lib.model."""
         self.test_issue__repr__()
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
         issues = pagure.lib.query.search_issues(self.session, repo)
@@ -171,7 +171,7 @@ class PagureLibModeltests(tests.Modeltests):
         )
 
     def test_group_project_ordering(self):
-        """ Test the ordering of project.groups. """
+        """Test the ordering of project.groups."""
         # Create three projects
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
diff --git a/tests/test_pagure_lib_model_delete_project.py b/tests/test_pagure_lib_model_delete_project.py
index 3f6c78e..75cc2d2 100644
--- a/tests/test_pagure_lib_model_delete_project.py
+++ b/tests/test_pagure_lib_model_delete_project.py
@@ -28,10 +28,10 @@ import tests
 
 
 class DeleteProjectTests(tests.Modeltests):
-    """ Tests for flask issues controller of pagure """
+    """Tests for flask issues controller of pagure"""
 
     def test_delete_project_with_group(self):
-        """ Test the model when we delete a project with a group. """
+        """Test the model when we delete a project with a group."""
 
         # Create a project
         item = model.Project(
@@ -76,7 +76,7 @@ class DeleteProjectTests(tests.Modeltests):
         self.assertEqual(self.session.query(model.ProjectGroup).count(), 0)
 
     def test_delete_project_with_user(self):
-        """ Test the model when we delete a project with users. """
+        """Test the model when we delete a project with users."""
 
         # Create a project
         item = model.Project(
@@ -112,7 +112,7 @@ class DeleteProjectTests(tests.Modeltests):
         self.assertEqual(self.session.query(model.User).count(), 2)
 
     def test_delete_project_with_coloredtags(self):
-        """ Test the model when we delete a project with Colored tags. """
+        """Test the model when we delete a project with Colored tags."""
 
         # Create a project
         item = model.Project(
@@ -148,8 +148,8 @@ class DeleteProjectTests(tests.Modeltests):
         self.assertEqual(self.session.query(model.TagColored).count(), 0)
 
     def test_delete_project_with_coloredtags_and_issues(self):
-        """ Test the model when we delete a project with Colored tags and
-        issues. """
+        """Test the model when we delete a project with Colored tags and
+        issues."""
 
         # Create a project
         item = model.Project(
@@ -212,8 +212,8 @@ class DeleteProjectTests(tests.Modeltests):
         self.assertEqual(self.session.query(model.Issue).count(), 0)
 
     def test_delete_project_with_coloredtags_and_tagged_issues(self):
-        """ Test the model when we delete a project with Colored tags and
-        tagged issues. """
+        """Test the model when we delete a project with Colored tags and
+        tagged issues."""
 
         # Create a project
         item = model.Project(
diff --git a/tests/test_pagure_lib_notify.py b/tests/test_pagure_lib_notify.py
index 8d31cb7..2cb74d6 100644
--- a/tests/test_pagure_lib_notify.py
+++ b/tests/test_pagure_lib_notify.py
@@ -25,15 +25,16 @@ import pagure.lib.model
 import pagure.lib.notify
 import pagure.lib.query
 import tests
+import munch
 
 
 class PagureLibNotifytests(tests.Modeltests):
-    """ Tests for pagure.lib.notify """
+    """Tests for pagure.lib.notify"""
 
     maxDiff = None
 
     def test_get_emails_for_obj_issue(self):
-        """ Test the _get_emails_for_obj method from pagure.lib.notify. """
+        """Test the _get_emails_for_obj method from pagure.lib.notify."""
 
         # Create the project ns/test
         item = pagure.lib.model.Project(
@@ -99,7 +100,7 @@ class PagureLibNotifytests(tests.Modeltests):
         self.assertEqual(out, exp)
 
     def test_get_emails_for_obj_issue_watching_project(self):
-        """ Test the _get_emails_for_obj method from pagure.lib.notify. """
+        """Test the _get_emails_for_obj method from pagure.lib.notify."""
 
         # Create the project ns/test
         item = pagure.lib.model.Project(
@@ -173,7 +174,7 @@ class PagureLibNotifytests(tests.Modeltests):
 
     @patch("pagure.lib.notify.smtplib.SMTP")
     def test_get_emails_for_obj_pr(self, mock_smtp):
-        """ Test the _get_emails_for_obj method from pagure.lib.notify. """
+        """Test the _get_emails_for_obj method from pagure.lib.notify."""
         mock_smtp.return_value = MagicMock()
 
         tests.create_projects(self.session)
@@ -250,7 +251,7 @@ class PagureLibNotifytests(tests.Modeltests):
 
     @patch("pagure.lib.notify.smtplib.SMTP")
     def test_get_emails_for_obj_pr_watching_project(self, mock_smtp):
-        """ Test the _get_emails_for_obj method from pagure.lib.notify. """
+        """Test the _get_emails_for_obj method from pagure.lib.notify."""
         mock_smtp.return_value = MagicMock()
 
         tests.create_projects(self.session)
@@ -331,7 +332,7 @@ class PagureLibNotifytests(tests.Modeltests):
         self.assertEqual(out, exp)
 
     def test_get_emails_for_obj_private_issue(self):
-        """ Test the _get_emails_for_obj method from pagure.lib.notify. """
+        """Test the _get_emails_for_obj method from pagure.lib.notify."""
 
         # Create the project ns/test
         item = pagure.lib.model.Project(
@@ -412,7 +413,7 @@ class PagureLibNotifytests(tests.Modeltests):
     )
     @patch("pagure.lib.notify.smtplib.SMTP")
     def test_send_email(self, mock_smtp):
-        """ Test the send_email method from pagure.lib.notify. """
+        """Test the send_email method from pagure.lib.notify."""
         mock_smtp.return_value = MagicMock()
 
         email = pagure.lib.notify.send_email(
@@ -499,8 +500,8 @@ RW1haWwgY29udGVudA==
     @patch.dict("pagure.config.config", {"EVENTSOURCE_SOURCE": None})
     @patch("pagure.lib.notify.smtplib.SMTP")
     def test_send_email_no_reply_to(self, mock_smtp):
-        """ Test the send_email method from pagure.lib.notify when there
-        should not be a Reply-To header even if mail_id is defined. """
+        """Test the send_email method from pagure.lib.notify when there
+        should not be a Reply-To header even if mail_id is defined."""
         mock_smtp.return_value = MagicMock()
 
         email = pagure.lib.notify.send_email(
@@ -543,6 +544,31 @@ RW1haWwgY29udGVudA==
 """
         self.assertEqual(email.as_string(), exp)
 
+    def test_notification_mention(self):
+        g = munch.Munch()
+        g.session = self.session
+        with patch("flask.g", g):
+
+            def _check_mention(comment, exp):
+                emails = set([])
+                emails = pagure.lib.notify._add_mentioned_users(
+                    emails, comment
+                )
+
+                self.assertEqual(emails, exp)
+
+            exp = set(["bar@pingou.com"])
+            comment = "I think we should ask @pingou how to pronounce pagure"
+            _check_mention(comment, exp)
+
+            exp = set([])
+            comment = """Let me quote him:
+~~~~
+ @pingou> Pagure is pronounced 'pa-gure', not 'pagu-re'
+~~~~
+"""
+            _check_mention(comment, exp)
+
 
 if __name__ == "__main__":
     unittest.main(verbosity=2)
diff --git a/tests/test_pagure_lib_notify_email.py b/tests/test_pagure_lib_notify_email.py
index b0d9ea4..28e1db3 100644
--- a/tests/test_pagure_lib_notify_email.py
+++ b/tests/test_pagure_lib_notify_email.py
@@ -29,12 +29,12 @@ import tests  # pylint: disable=wrong-import-position
 
 
 class PagureLibNotifyEmailtests(tests.Modeltests):
-    """ Some tests for the various email construction functions. In
+    """Some tests for the various email construction functions. In
     their own class so they can have some shared fixtures.
     """
 
     def setUp(self):
-        """ Override setUp to add more fixtures used for many tests. """
+        """Override setUp to add more fixtures used for many tests."""
         super(PagureLibNotifyEmailtests, self).setUp()
 
         tests.create_projects(self.session)
@@ -255,6 +255,150 @@ http://localhost.localdomain/test/issue/1
         # Mail should be from user1 (who wrote the comment).
         self.assertEqual(kwargs["user_from"], self.user1.fullname)
 
+    @mock.patch("pagure.lib.notify.send_email")
+    def test_user_notified_new_comment_not_in_code_block_incline(
+        self, fakemail
+    ):
+        """Ensures that @mention doesn't over-reach in code-blocks."""
+        self.comment1.comment = (
+            "So apparently they said ``@foo.com is awesome`` :)"
+        )
+        g = munch.Munch()
+        g.fas_user = tests.FakeUser(username="pingou")
+        g.authenticated = True
+        g.session = self.session
+        with mock.patch("flask.g", g):
+            pagure.lib.notify.notify_new_comment(self.comment1)
+
+        (_, args, kwargs) = fakemail.mock_calls[0]
+
+        # Mail should be sent to both users
+        self.assertEqual(args[2], "bar@pingou.com")
+
+        # Mail ID should be comment #1's mail ID...
+        self.assertEqual(kwargs["mail_id"], self.comment1.mail_id)
+
+        # In reply to issue #1's mail ID.
+        self.assertEqual(kwargs["in_reply_to"], self.issue1.mail_id)
+
+        # Project name should be...project (full) name.
+        self.assertEqual(kwargs["project_name"], self.project1.fullname)
+
+        # Mail should be from user1 (who wrote the comment).
+        self.assertEqual(kwargs["user_from"], self.user1.fullname)
+
+    @mock.patch("pagure.lib.notify.send_email")
+    def test_user_notified_new_comment_not_in_code_block_4_backtick(
+        self, fakemail
+    ):
+        """Ensures that @mention doesn't over-reach in code-blocks."""
+        self.comment1.comment = """
+So apparently they said
+````
+@foo.com is awesome and @foo is great
+
+We all love @foo !
+````
+:)
+"""
+        g = munch.Munch()
+        g.fas_user = tests.FakeUser(username="pingou")
+        g.authenticated = True
+        g.session = self.session
+        with mock.patch("flask.g", g):
+            pagure.lib.notify.notify_new_comment(self.comment1)
+
+        (_, args, kwargs) = fakemail.mock_calls[0]
+
+        # Mail should be sent to both users
+        self.assertEqual(args[2], "bar@pingou.com")
+
+        # Mail ID should be comment #1's mail ID...
+        self.assertEqual(kwargs["mail_id"], self.comment1.mail_id)
+
+        # In reply to issue #1's mail ID.
+        self.assertEqual(kwargs["in_reply_to"], self.issue1.mail_id)
+
+        # Project name should be...project (full) name.
+        self.assertEqual(kwargs["project_name"], self.project1.fullname)
+
+        # Mail should be from user1 (who wrote the comment).
+        self.assertEqual(kwargs["user_from"], self.user1.fullname)
+
+    @mock.patch("pagure.lib.notify.send_email")
+    def test_user_notified_new_comment_not_in_code_block_3_backticks(
+        self, fakemail
+    ):
+        """Ensures that @mention doesn't over-reach in code-blocks."""
+        self.comment1.comment = """
+So apparently they said
+```
+@foo.com is awesome and @foo is great
+
+We all love @foo !
+```
+:)
+"""
+        g = munch.Munch()
+        g.fas_user = tests.FakeUser(username="pingou")
+        g.authenticated = True
+        g.session = self.session
+        with mock.patch("flask.g", g):
+            pagure.lib.notify.notify_new_comment(self.comment1)
+
+        (_, args, kwargs) = fakemail.mock_calls[0]
+
+        # Mail should be sent to both users
+        self.assertEqual(args[2], "bar@pingou.com")
+
+        # Mail ID should be comment #1's mail ID...
+        self.assertEqual(kwargs["mail_id"], self.comment1.mail_id)
+
+        # In reply to issue #1's mail ID.
+        self.assertEqual(kwargs["in_reply_to"], self.issue1.mail_id)
+
+        # Project name should be...project (full) name.
+        self.assertEqual(kwargs["project_name"], self.project1.fullname)
+
+        # Mail should be from user1 (who wrote the comment).
+        self.assertEqual(kwargs["user_from"], self.user1.fullname)
+
+    @mock.patch("pagure.lib.notify.send_email")
+    def test_user_notified_new_comment_not_in_code_block_tilde(self, fakemail):
+        """Ensures that @mention doesn't over-reach in code-blocks."""
+        self.comment1.comment = """
+So apparently they said
+~~~~
+@foo.com is awesome and @foo is great
+
+We all love @foo !
+~~~~
+:)
+"""
+        g = munch.Munch()
+        g.fas_user = tests.FakeUser(username="pingou")
+        g.authenticated = True
+        g.session = self.session
+        with mock.patch("flask.g", g):
+            pagure.lib.notify.notify_new_comment(self.comment1)
+
+        (_, args, kwargs) = fakemail.mock_calls[0]
+
+        # Mail should be sent to both users
+        self.assertEqual(args[2], "bar@pingou.com")
+
+        # Mail ID should be comment #1's mail ID...
+        self.assertEqual(kwargs["mail_id"], self.comment1.mail_id)
+
+        # In reply to issue #1's mail ID.
+        self.assertEqual(kwargs["in_reply_to"], self.issue1.mail_id)
+
+        # Project name should be...project (full) name.
+        self.assertEqual(kwargs["project_name"], self.project1.fullname)
+
+        # Mail should be from user1 (who wrote the comment).
+        self.assertEqual(kwargs["user_from"], self.user1.fullname)
+
     @mock.patch("pagure.lib.notify.send_email")
     def test_notify_new_issue_namespaced(
         self, fakemail
diff --git a/tests/test_pagure_lib_plugins.py b/tests/test_pagure_lib_plugins.py
index 7163f67..ecf2986 100644
--- a/tests/test_pagure_lib_plugins.py
+++ b/tests/test_pagure_lib_plugins.py
@@ -45,7 +45,7 @@ class PagureLibtests_plugins(tests.Modeltests):
 
     @patch("pagure.lib.plugins.load")
     def test_plugin_is_enabled_for(self, load):
-        """ Test the is_enabled_for method of plugins is properly
+        """Test the is_enabled_for method of plugins is properly
         handled by pagure.lib.plugins.get_enabled_plugins.
         """
         tests.create_projects(self.session)
@@ -62,7 +62,7 @@ class PagureLibtests_plugins(tests.Modeltests):
 
     @patch("pagure.lib.plugins.load")
     def test_get_plugin_names(self, load):
-        """ Test the get_plugin_names method with plugins that don't
+        """Test the get_plugin_names method with plugins that don't
         have backref.
         """
         load.return_value = [EnabledForAll]
diff --git a/tests/test_pagure_lib_star_project.py b/tests/test_pagure_lib_star_project.py
index 8f2b4b6..3123c46 100644
--- a/tests/test_pagure_lib_star_project.py
+++ b/tests/test_pagure_lib_star_project.py
@@ -24,15 +24,15 @@ import tests
 
 
 class TestStarProjectLib(tests.SimplePagureTest):
-    """ Test the star project feature of pagure """
+    """Test the star project feature of pagure"""
 
     def setUp(self):
-        """ Set up the environnment for running each star project lib tests """
+        """Set up the environnment for running each star project lib tests"""
         super(TestStarProjectLib, self).setUp()
         tests.create_projects(self.session)
 
     def test_update_star_project(self):
-        """ Test the update_star_project endpoint of pagure.lib """
+        """Test the update_star_project endpoint of pagure.lib"""
 
         repo_obj = pagure.lib.query._get_project(self.session, "test")
         # test with invalud Star object, should return None
@@ -67,7 +67,7 @@ class TestStarProjectLib(tests.SimplePagureTest):
         self.assertEqual(len(user_obj.stars), 0)
 
     def test_star_project(self):
-        """ Test the _star_project endpoint of pagure.lib """
+        """Test the _star_project endpoint of pagure.lib"""
 
         # test with not all arguments present
         user_obj = pagure.lib.query.get_user(self.session, "pingou")
@@ -85,7 +85,7 @@ class TestStarProjectLib(tests.SimplePagureTest):
         self.assertEqual(len(user_obj.stars), 1)
 
     def test_unstar_project(self):
-        """ Test the _unstar_project endpoint of pagure.lib """
+        """Test the _unstar_project endpoint of pagure.lib"""
 
         # test with not all arguments present
         user_obj = pagure.lib.query.get_user(self.session, "pingou")
@@ -123,7 +123,7 @@ class TestStarProjectLib(tests.SimplePagureTest):
         self.assertEqual(len(user_obj.stars), 0)
 
     def test_get_stargazer_obj(self):
-        """ Test the _get_stargazer_obj test of pagure.lib """
+        """Test the _get_stargazer_obj test of pagure.lib"""
 
         # star the project first
         repo_obj = pagure.lib.query._get_project(self.session, "test")
@@ -161,7 +161,7 @@ class TestStarProjectLib(tests.SimplePagureTest):
         self.assertEqual(star_obj is None, True)
 
     def test_has_starred(self):
-        """ Test the has_starred endpoint of pagure.lib """
+        """Test the has_starred endpoint of pagure.lib"""
 
         # star the project
         repo_obj = pagure.lib.query._get_project(self.session, "test")
diff --git a/tests/test_pagure_lib_task_mirror.py b/tests/test_pagure_lib_task_mirror.py
index 647dc25..f21df35 100644
--- a/tests/test_pagure_lib_task_mirror.py
+++ b/tests/test_pagure_lib_task_mirror.py
@@ -35,12 +35,12 @@ import pagure.lib.tasks_mirror
 
 
 class PagureLibTaskMirrortests(tests.Modeltests):
-    """ Tests for pagure.lib.task_mirror """
+    """Tests for pagure.lib.task_mirror"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureLibTaskMirrortests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -50,7 +50,7 @@ class PagureLibTaskMirrortests(tests.Modeltests):
         tests.create_projects(self.session)
 
     def test_create_ssh_key(self):
-        """ Test the _create_ssh_key method. """
+        """Test the _create_ssh_key method."""
         # before
         self.assertFalse(os.path.exists(self.sshkeydir))
         os.mkdir(self.sshkeydir)
@@ -65,7 +65,7 @@ class PagureLibTaskMirrortests(tests.Modeltests):
         )
 
     def test_setup_mirroring(self):
-        """ Test the setup_mirroring method. """
+        """Test the setup_mirroring method."""
 
         # before
         self.assertFalse(os.path.exists(self.sshkeydir))
@@ -92,7 +92,7 @@ class PagureLibTaskMirrortests(tests.Modeltests):
         self.assertTrue(project.mirror_hook.public_key.startswith("ssh-rsa "))
 
     def test_setup_mirroring_ssh_folder_exists_wrong_permissions(self):
-        """ Test the setup_mirroring method. """
+        """Test the setup_mirroring method."""
 
         os.makedirs(self.sshkeydir)
 
@@ -122,7 +122,7 @@ class PagureLibTaskMirrortests(tests.Modeltests):
         self.assertIsNone(project.mirror_hook.public_key)
 
     def test_setup_mirroring_ssh_folder_symlink(self):
-        """ Test the setup_mirroring method. """
+        """Test the setup_mirroring method."""
 
         os.symlink(self.path, self.sshkeydir)
 
@@ -175,7 +175,7 @@ class PagureLibTaskMirrortests(tests.Modeltests):
 
     @patch("os.getuid", MagicMock(return_value=450))
     def test_setup_mirroring_ssh_folder_owner(self):
-        """ Test the setup_mirroring method. """
+        """Test the setup_mirroring method."""
         os.makedirs(self.sshkeydir, mode=0o700)
 
         # before
@@ -205,12 +205,12 @@ class PagureLibTaskMirrortests(tests.Modeltests):
 
 
 class PagureLibTaskMirrorSetuptests(tests.Modeltests):
-    """ Tests for pagure.lib.task_mirror """
+    """Tests for pagure.lib.task_mirror"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureLibTaskMirrorSetuptests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -233,7 +233,7 @@ class PagureLibTaskMirrorSetuptests(tests.Modeltests):
         )
 
     def test_setup_mirroring_twice(self):
-        """ Test the setup_mirroring method. """
+        """Test the setup_mirroring method."""
 
         # before
         self.assertEqual(
@@ -261,7 +261,7 @@ class PagureLibTaskMirrorSetuptests(tests.Modeltests):
         self.assertEqual(project.mirror_hook.public_key, before_key)
 
     def test_teardown_mirroring(self):
-        """ Test the teardown_mirroring method. """
+        """Test the teardown_mirroring method."""
 
         # before
         self.assertEqual(
@@ -283,7 +283,7 @@ class PagureLibTaskMirrorSetuptests(tests.Modeltests):
 
     @patch("pagure.lib.git.read_git_lines")
     def test_mirror_project(self, rgl):
-        """ Test the mirror_project method. """
+        """Test the mirror_project method."""
         rgl.return_value = ("stdout", "stderr")
         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)
 
diff --git a/tests/test_pagure_lib_task_services.py b/tests/test_pagure_lib_task_services.py
index d2b9a2a..7142fc2 100644
--- a/tests/test_pagure_lib_task_services.py
+++ b/tests/test_pagure_lib_task_services.py
@@ -34,12 +34,12 @@ import pagure.lib.tasks_services
 
 
 class PagureLibTaskServicestests(tests.Modeltests):
-    """ Tests for pagure.lib.task_services """
+    """Tests for pagure.lib.task_services"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureLibTaskServicestests, self).setUp()
 
         tests.create_projects(self.session)
@@ -63,7 +63,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
         self.session.commit()
 
     def test_webhook_notification_invalid_project(self):
-        """ Test the webhook_notification method. """
+        """Test the webhook_notification method."""
 
         self.assertRaises(
             RuntimeError,
@@ -77,7 +77,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.call_web_hooks")
     def test_webhook_notification_no_webhook(self, call_wh):
-        """ Test the webhook_notification method. """
+        """Test the webhook_notification method."""
 
         output = pagure.lib.tasks_services.webhook_notification(
             topic="topic",
@@ -91,7 +91,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
 
     @patch("pagure.lib.git.log_commits_to_db")
     def test_log_commit_send_notifications_invalid_project(self, log):
-        """ Test the log_commit_send_notifications method. """
+        """Test the log_commit_send_notifications method."""
         output = pagure.lib.tasks_services.log_commit_send_notifications(
             name="invalid",
             commits=[],
@@ -107,7 +107,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
     @patch("pagure.lib.notify.notify_new_commits")
     @patch("pagure.lib.git.log_commits_to_db")
     def test_log_commit_send_notifications_valid_project(self, log, notif):
-        """ Test the log_commit_send_notifications method. """
+        """Test the log_commit_send_notifications method."""
         output = pagure.lib.tasks_services.log_commit_send_notifications(
             name="test",
             commits=["hash1", "hash2"],
@@ -127,7 +127,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.trigger_jenkins_build")
     def test_trigger_ci_build_invalid_project(self, trigger_jenk):
-        """ Test the trigger_ci_build method. """
+        """Test the trigger_ci_build method."""
         output = pagure.lib.tasks_services.trigger_ci_build(
             project_name="invalid",
             cause="PR#ID",
@@ -140,7 +140,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.trigger_jenkins_build")
     def test_trigger_ci_build_not_configured_project(self, trigger_jenk):
-        """ Test the trigger_ci_build method. """
+        """Test the trigger_ci_build method."""
         self.assertRaises(
             pagure.exceptions.PagureException,
             pagure.lib.tasks_services.trigger_ci_build,
@@ -154,7 +154,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.trigger_jenkins_build")
     def test_trigger_ci_build_not_configured_project_fork(self, trigger_jenk):
-        """ Test the trigger_ci_build method. """
+        """Test the trigger_ci_build method."""
         self.assertRaises(
             pagure.exceptions.PagureException,
             pagure.lib.tasks_services.trigger_ci_build,
@@ -168,7 +168,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
 
     @patch("pagure.lib.query._get_project")
     def test_load_json_commits_to_db_invalid_data_type(self, get_project):
-        """ Test the load_json_commits_to_db method. """
+        """Test the load_json_commits_to_db method."""
         output = pagure.lib.tasks_services.load_json_commits_to_db(
             name="test",
             commits=["hash1", "hash2"],
@@ -183,7 +183,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.get_files_to_load")
     def test_load_json_commits_to_db_invalid_project(self, get_files):
-        """ Test the load_json_commits_to_db method. """
+        """Test the load_json_commits_to_db method."""
         output = pagure.lib.tasks_services.load_json_commits_to_db(
             name="invalid",
             commits=["hash1", "hash2"],
@@ -199,7 +199,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
     @patch("pagure.lib.git.update_request_from_git")
     @patch("pagure.lib.git.update_ticket_from_git")
     def test_load_json_commits_to_db_invalid_path(self, up_issue, up_pr):
-        """ Test the load_json_commits_to_db method. """
+        """Test the load_json_commits_to_db method."""
         output = pagure.lib.tasks_services.load_json_commits_to_db(
             name="test",
             commits=["hash1", "hash2"],
@@ -218,7 +218,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
     def test_load_json_commits_to_db_invalid_path_one_commit(
         self, up_issue, up_pr
     ):
-        """ Test the load_json_commits_to_db method. """
+        """Test the load_json_commits_to_db method."""
         output = pagure.lib.tasks_services.load_json_commits_to_db(
             name="test",
             commits=["hash1"],
@@ -236,7 +236,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
     @patch("pagure.lib.git.update_request_from_git")
     @patch("pagure.lib.git.update_ticket_from_git")
     def test_load_json_commits_to_db_no_agent(self, up_issue, up_pr, send):
-        """ Test the load_json_commits_to_db method. """
+        """Test the load_json_commits_to_db method."""
         output = pagure.lib.tasks_services.load_json_commits_to_db(
             name="test",
             commits=[],
@@ -258,7 +258,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
     def test_load_json_commits_to_db_no_agent(
         self, git, up_issue, up_pr, send
     ):
-        """ Test the load_json_commits_to_db method. """
+        """Test the load_json_commits_to_db method."""
         git.side_effect = [["file1"], ["file2"], ["files/image"], ["file1"]]
 
         output = pagure.lib.tasks_services.load_json_commits_to_db(
@@ -283,7 +283,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
     def test_load_json_commits_to_db_tickets(
         self, git, up_issue, up_pr, send, json_loads
     ):
-        """ Test the load_json_commits_to_db method. """
+        """Test the load_json_commits_to_db method."""
         git.side_effect = [["file1"], ["file2"], ["files/image"], ["file1"]]
         json_loads.return_value = "foobar"
 
@@ -330,7 +330,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
     def test_load_json_commits_to_db_prs(
         self, git, up_issue, up_pr, send, json_loads
     ):
-        """ Test the load_json_commits_to_db method. """
+        """Test the load_json_commits_to_db method."""
         git.side_effect = [["file1"], ["file2"], ["files/image"], ["file1"]]
         json_loads.return_value = "foobar"
 
@@ -388,7 +388,7 @@ class PagureLibTaskServicestests(tests.Modeltests):
     def test_load_json_commits_to_db_prs_raises_error(
         self, git, up_issue, up_pr, send, json_loads
     ):
-        """ Test the load_json_commits_to_db method. """
+        """Test the load_json_commits_to_db method."""
         git.side_effect = [["file1"], ["file2"], ["files/image"], ["file1"]]
         json_loads.return_value = "foobar"
         up_pr.side_effect = Exception("foo error")
@@ -433,12 +433,12 @@ class PagureLibTaskServicestests(tests.Modeltests):
 
 
 class PagureLibTaskServicesWithWebHooktests(tests.Modeltests):
-    """ Tests for pagure.lib.task_services """
+    """Tests for pagure.lib.task_services"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureLibTaskServicesWithWebHooktests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -455,7 +455,7 @@ class PagureLibTaskServicesWithWebHooktests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.call_web_hooks")
     def test_webhook_notification_no_webhook(self, call_wh):
-        """ Test the webhook_notification method. """
+        """Test the webhook_notification method."""
 
         output = pagure.lib.tasks_services.webhook_notification(
             topic="topic",
@@ -479,7 +479,7 @@ class PagureLibTaskServicesWithWebHooktests(tests.Modeltests):
     @patch("datetime.datetime")
     @patch("requests.post")
     def test_webhook_notification_no_webhook(self, post, dt):
-        """ Test the webhook_notification method. """
+        """Test the webhook_notification method."""
         post.return_value = False
         utcnow = MagicMock()
         utcnow.year = 2018
@@ -552,12 +552,12 @@ class PagureLibTaskServicesWithWebHooktests(tests.Modeltests):
 
 
 class PagureLibTaskServicesJenkinsCItests(tests.Modeltests):
-    """ Tests for pagure.lib.task_services """
+    """Tests for pagure.lib.task_services"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureLibTaskServicesJenkinsCItests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -597,7 +597,7 @@ class PagureLibTaskServicesJenkinsCItests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.trigger_jenkins_build")
     def test_trigger_ci_build_invalid_ci(self, trigger_jenk):
-        """ Test the trigger_ci_build method. """
+        """Test the trigger_ci_build method."""
         output = pagure.lib.tasks_services.trigger_ci_build(
             project_name="test",
             cause="PR#ID",
@@ -610,7 +610,7 @@ class PagureLibTaskServicesJenkinsCItests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.trigger_jenkins_build")
     def test_trigger_ci_build_invalid_ci_fork(self, trigger_jenk):
-        """ Test the trigger_ci_build method. """
+        """Test the trigger_ci_build method."""
         output = pagure.lib.tasks_services.trigger_ci_build(
             project_name="forks/foo/test",
             cause="PR#ID",
@@ -623,7 +623,7 @@ class PagureLibTaskServicesJenkinsCItests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.trigger_jenkins_build")
     def test_trigger_ci_build_valid_project(self, trigger_jenk):
-        """ Test the trigger_ci_build method. """
+        """Test the trigger_ci_build method."""
         output = pagure.lib.tasks_services.trigger_ci_build(
             project_name="test",
             cause="PR#ID",
@@ -646,7 +646,7 @@ class PagureLibTaskServicesJenkinsCItests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.trigger_jenkins_build")
     def test_trigger_ci_build_valid_project_fork(self, trigger_jenk):
-        """ Test the trigger_ci_build method. """
+        """Test the trigger_ci_build method."""
         output = pagure.lib.tasks_services.trigger_ci_build(
             project_name="forks/foo/test",
             cause="PR#ID",
@@ -669,12 +669,12 @@ class PagureLibTaskServicesJenkinsCItests(tests.Modeltests):
 
 
 class PagureLibTaskServicesJenkinsCIAuthtests(tests.Modeltests):
-    """ Tests for pagure.lib.task_services """
+    """Tests for pagure.lib.task_services"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureLibTaskServicesJenkinsCIAuthtests, self).setUp()
 
         pagure.config.config["REQUESTS_FOLDER"] = None
@@ -716,7 +716,7 @@ class PagureLibTaskServicesJenkinsCIAuthtests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.trigger_jenkins_build")
     def test_trigger_ci_build_invalid_ci(self, trigger_jenk):
-        """ Test the trigger_ci_build method. """
+        """Test the trigger_ci_build method."""
         output = pagure.lib.tasks_services.trigger_ci_build(
             project_name="test",
             cause="PR#ID",
@@ -729,7 +729,7 @@ class PagureLibTaskServicesJenkinsCIAuthtests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.trigger_jenkins_build")
     def test_trigger_ci_build_invalid_ci_fork(self, trigger_jenk):
-        """ Test the trigger_ci_build method. """
+        """Test the trigger_ci_build method."""
         output = pagure.lib.tasks_services.trigger_ci_build(
             project_name="forks/foo/test",
             cause="PR#ID",
@@ -742,7 +742,7 @@ class PagureLibTaskServicesJenkinsCIAuthtests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.trigger_jenkins_build")
     def test_trigger_ci_build_valid_project(self, trigger_jenk):
-        """ Test the trigger_ci_build method. """
+        """Test the trigger_ci_build method."""
         output = pagure.lib.tasks_services.trigger_ci_build(
             project_name="test",
             cause="PR#ID",
@@ -765,7 +765,7 @@ class PagureLibTaskServicesJenkinsCIAuthtests(tests.Modeltests):
 
     @patch("pagure.lib.tasks_services.trigger_jenkins_build")
     def test_trigger_ci_build_valid_project_fork(self, trigger_jenk):
-        """ Test the trigger_ci_build method. """
+        """Test the trigger_ci_build method."""
         output = pagure.lib.tasks_services.trigger_ci_build(
             project_name="forks/foo/test",
             cause="PR#ID",
@@ -788,12 +788,12 @@ class PagureLibTaskServicesJenkinsCIAuthtests(tests.Modeltests):
 
 
 class PagureLibTaskServicesLoadJsonTickettests(tests.Modeltests):
-    """ Tests for pagure.lib.task_services """
+    """Tests for pagure.lib.task_services"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureLibTaskServicesLoadJsonTickettests, self).setUp()
 
         tests.create_projects(self.session)
@@ -845,7 +845,7 @@ class PagureLibTaskServicesLoadJsonTickettests(tests.Modeltests):
     @patch("pagure.lib.notify.send_email")
     @patch("pagure.lib.git.update_request_from_git")
     def test_loading_issue_json(self, up_pr, send):
-        """ Test loading the JSON file of a ticket. """
+        """Test loading the JSON file of a ticket."""
         project = pagure.lib.query.get_authorized_project(self.session, "test")
         issue = pagure.lib.query.search_issues(
             self.session, project, issueid=1
diff --git a/tests/test_pagure_lib_watch_list.py b/tests/test_pagure_lib_watch_list.py
index 29049ee..593089d 100644
--- a/tests/test_pagure_lib_watch_list.py
+++ b/tests/test_pagure_lib_watch_list.py
@@ -29,10 +29,10 @@ import tests
 @mock.patch("pagure.lib.git.update_git", mock.MagicMock(return_value=True))
 @mock.patch("pagure.lib.notify.send_email", mock.MagicMock(return_value=True))
 class PagureLibGetWatchListtests(tests.Modeltests):
-    """ Tests for pagure.lib.query.get_watch_list """
+    """Tests for pagure.lib.query.get_watch_list"""
 
     def test_get_watch_list_invalid_object(self):
-        """ Test get_watch_list when given an invalid object """
+        """Test get_watch_list when given an invalid object"""
         # Create a project ns/test
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
@@ -53,8 +53,8 @@ class PagureLibGetWatchListtests(tests.Modeltests):
         )
 
     def test_get_watch_list_simple(self):
-        """ Test get_watch_list when the creator of the ticket is the
-        creator of the project """
+        """Test get_watch_list when the creator of the ticket is the
+        creator of the project"""
         # Create a project ns/test
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
@@ -85,8 +85,8 @@ class PagureLibGetWatchListtests(tests.Modeltests):
         )
 
     def test_get_watch_list_different_creator(self):
-        """ Test get_watch_list when the creator of the ticket is not the
-        creator of the project """
+        """Test get_watch_list when the creator of the ticket is not the
+        creator of the project"""
         # Create a project ns/test
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
@@ -118,8 +118,8 @@ class PagureLibGetWatchListtests(tests.Modeltests):
         )
 
     def test_get_watch_list_project_w_contributor(self):
-        """ Test get_watch_list when the project has more than one
-        contributor """
+        """Test get_watch_list when the project has more than one
+        contributor"""
         # Create a project ns/test3
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
@@ -174,8 +174,7 @@ class PagureLibGetWatchListtests(tests.Modeltests):
         )
 
     def test_get_watch_list_user_in_group(self):
-        """ Test get_watch_list when the project has groups of contributors
-        """
+        """Test get_watch_list when the project has groups of contributors"""
         # Create a project ns/test3
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
@@ -258,8 +257,8 @@ class PagureLibGetWatchListtests(tests.Modeltests):
         )
 
     def test_get_watch_list_project_w_contributor_out(self):
-        """ Test get_watch_list when the project has one contributor not
-        watching the project """
+        """Test get_watch_list when the project has one contributor not
+        watching the project"""
         # Create a project ns/test3
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
@@ -321,8 +320,8 @@ class PagureLibGetWatchListtests(tests.Modeltests):
         )
 
     def test_get_watch_list_project_w_contributor_out_pr(self):
-        """ Test get_watch_list when the project has one contributor not
-        watching the pull-request """
+        """Test get_watch_list when the project has one contributor not
+        watching the pull-request"""
         # Create a project ns/test3
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
@@ -384,8 +383,8 @@ class PagureLibGetWatchListtests(tests.Modeltests):
         )
 
     def test_get_watch_list_project_w_contributor_watching_project(self):
-        """ Test get_watch_list when the project has one contributor watching
-        the project """
+        """Test get_watch_list when the project has one contributor watching
+        the project"""
         # Create a project ns/test3
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
@@ -442,8 +441,8 @@ class PagureLibGetWatchListtests(tests.Modeltests):
 
     @mock.patch.dict("pagure.config.config", {"PAGURE_ADMIN_USERS": "foo"})
     def test_get_watch_list_project_w_private_issue(self):
-        """ Test get_watch_list when the project has one contributor watching
-        the project and the issue is private """
+        """Test get_watch_list when the project has one contributor watching
+        the project and the issue is private"""
         # Create a project ns/test3
         item = pagure.lib.model.Project(
             user_id=1,  # pingou
diff --git a/tests/test_pagure_merge_pr_no_fork.py b/tests/test_pagure_merge_pr_no_fork.py
index 8ab13e3..51970d1 100644
--- a/tests/test_pagure_merge_pr_no_fork.py
+++ b/tests/test_pagure_merge_pr_no_fork.py
@@ -28,12 +28,12 @@ import tests
 
 
 class PagureMergePrNoForkTest(tests.Modeltests):
-    """ Tests merging a PR in pagure when the fork no longer exists """
+    """Tests merging a PR in pagure when the fork no longer exists"""
 
     maxDiff = None
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureMergePrNoForkTest, self).setUp()
 
         tests.create_projects(self.session)
@@ -94,7 +94,7 @@ class PagureMergePrNoForkTest(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_diffstats(self):
-        """ Test the api_pull_request_merge method of the flask api. """
+        """Test the api_pull_request_merge method of the flask api."""
 
         # Check the PR stats in the API
         output = self.app.get("/api/0/test/pull-request/1/diffstats")
@@ -116,7 +116,7 @@ class PagureMergePrNoForkTest(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_diffstats_no_fork(self):
-        """ Test the api_pull_request_merge method of the flask api. """
+        """Test the api_pull_request_merge method of the flask api."""
 
         pagure.lib.tasks.delete_project(
             namespace=None, name="test", user="pingou", action_user="pingou"
@@ -142,7 +142,7 @@ class PagureMergePrNoForkTest(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_merge(self):
-        """ Test the api_pull_request_merge method of the flask api. """
+        """Test the api_pull_request_merge method of the flask api."""
 
         headers = {"Authorization": "token aaabbbcccddd"}
 
@@ -156,7 +156,7 @@ class PagureMergePrNoForkTest(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_api_pull_request_merge_no_fork(self):
-        """ Test the api_pull_request_merge method of the flask api. """
+        """Test the api_pull_request_merge method of the flask api."""
 
         pagure.lib.tasks.delete_project(
             namespace=None, name="test", user="pingou", action_user="pingou"
@@ -174,7 +174,7 @@ class PagureMergePrNoForkTest(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_ui_pull_request_merge(self):
-        """ Test the api_pull_request_merge method of the flask UI. """
+        """Test the api_pull_request_merge method of the flask UI."""
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
@@ -197,7 +197,7 @@ class PagureMergePrNoForkTest(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_ui_pull_request_merge_no_fork(self):
-        """ Test the api_pull_request_merge method of the flask UI. """
+        """Test the api_pull_request_merge method of the flask UI."""
 
         pagure.lib.tasks.delete_project(
             namespace=None, name="test", user="pingou", action_user="pingou"
@@ -226,7 +226,7 @@ class PagureMergePrNoForkTest(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_internal_merge_status(self):
-        """ Test the api_pull_request_merge method of the flask UI. """
+        """Test the api_pull_request_merge method of the flask UI."""
 
         self.session = pagure.lib.query.create_session(self.dbpath)
         project = pagure.lib.query.get_authorized_project(self.session, "test")
@@ -254,7 +254,7 @@ class PagureMergePrNoForkTest(tests.Modeltests):
 
     @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
     def test_internal_merge_status_no_fork(self):
-        """ Test the api_pull_request_merge method of the flask UI. """
+        """Test the api_pull_request_merge method of the flask UI."""
 
         pagure.lib.tasks.delete_project(
             namespace=None, name="test", user="pingou", action_user="pingou"
diff --git a/tests/test_pagure_repospanner.py b/tests/test_pagure_repospanner.py
index 9189710..b2ef3a1 100644
--- a/tests/test_pagure_repospanner.py
+++ b/tests/test_pagure_repospanner.py
@@ -101,14 +101,14 @@ hooks:
 
 
 class PagureRepoSpannerTests(tests.Modeltests):
-    """ Tests for repoSpanner integration of pagure """
+    """Tests for repoSpanner integration of pagure"""
 
     repospanner_binary = None
     repospanner_runlog = None
     repospanner_proc = None
 
     def run_cacmd(self, logfile, *args):
-        """ Run a repoSpanner CA command. """
+        """Run a repoSpanner CA command."""
         subprocess.check_call(
             [
                 self.repospanner_binary,
@@ -124,7 +124,7 @@ class PagureRepoSpannerTests(tests.Modeltests):
         )
 
     def setUp(self):
-        """ set up the environment. """
+        """set up the environment."""
         possible_paths = ["./repospanner", "/usr/bin/repospanner"]
 
         for option in possible_paths:
@@ -303,7 +303,7 @@ class PagureRepoSpannerTests(tests.Modeltests):
         pagure.config.config["REPOSPANNER_REGIONS"]["default"]["hook"] = hookid
 
     def tearDown(self):
-        """ Tear down the repoSpanner instance. """
+        """Tear down the repoSpanner instance."""
         if self.repospanner_proc:
             # Tear down
             self.repospanner_proc.terminate()
@@ -340,7 +340,7 @@ class PagureRepoSpannerTestsNewRepoDefault(PagureRepoSpannerTests):
     @print_repospanner_log
     @patch("pagure.ui.app.admin_session_timedout")
     def test_new_project(self, ast):
-        """ Test creating a new repo by default on repoSpanner works. """
+        """Test creating a new repo by default on repoSpanner works."""
         ast.return_value = False
 
         user = tests.FakeUser(username="foo")
@@ -418,7 +418,7 @@ class PagureRepoSpannerTestsNewRepoDefault(PagureRepoSpannerTests):
         },
     )
     def test_http_pull(self):
-        """ Test that the HTTP pull endpoint works for repoSpanner. """
+        """Test that the HTTP pull endpoint works for repoSpanner."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -461,7 +461,7 @@ class PagureRepoSpannerTestsNewRepoDefault(PagureRepoSpannerTests):
         },
     )
     def test_http_push(self):
-        """ Test that the HTTP push endpoint works for repoSpanner. """
+        """Test that the HTTP push endpoint works for repoSpanner."""
         tests.create_projects(self.session)
         tests.create_tokens(self.session)
         tests.create_tokens_acl(self.session)
@@ -490,7 +490,7 @@ class PagureRepoSpannerTestsNewRepoDefault(PagureRepoSpannerTests):
     @print_repospanner_log
     @patch("pagure.ui.app.admin_session_timedout")
     def test_hooks(self, ast):
-        """ Test hook setting and running works. """
+        """Test hook setting and running works."""
         ast.return_value = False
         pagure.cli.admin.session = self.session
 
@@ -599,7 +599,7 @@ class PagureRepoSpannerTestsNewRepoDefault(PagureRepoSpannerTests):
     )
     @patch("pagure.ui.app.admin_session_timedout")
     def test_adopt_project(self, ast):
-        """ Test adopting a project in repoSpanner works. """
+        """Test adopting a project in repoSpanner works."""
         ast.return_value = False
 
         user = tests.FakeUser(username="foo")
diff --git a/tests/test_pagure_send_notification.py b/tests/test_pagure_send_notification.py
index f0de5b7..c6d8fac 100644
--- a/tests/test_pagure_send_notification.py
+++ b/tests/test_pagure_send_notification.py
@@ -24,10 +24,10 @@ import tests
 
 
 class PagureHooksDefault(tests.SimplePagureTest):
-    """ Tests for pagure.hooks.default """
+    """Tests for pagure.hooks.default"""
 
     def setUp(self):
-        """ Set up the environnment, ran before every tests. """
+        """Set up the environnment, ran before every tests."""
         super(PagureHooksDefault, self).setUp()
         tests.create_projects(self.session)
         self.projects = tests.create_projects_git(
diff --git a/tests/test_pagure_utils.py b/tests/test_pagure_utils.py
index dd33fde..e39b694 100644
--- a/tests/test_pagure_utils.py
+++ b/tests/test_pagure_utils.py
@@ -30,10 +30,10 @@ import tests
 
 
 class PagureUtilsTests(tests.SimplePagureTest):
-    """ Tests for pagure.utils """
+    """Tests for pagure.utils"""
 
     def setUp(self):
-        """ Set up the environnment, run before every tests. """
+        """Set up the environnment, run before every tests."""
         super(PagureUtilsTests, self).setUp()
 
         tests.create_projects(self.session)
@@ -57,25 +57,25 @@ class PagureUtilsTests(tests.SimplePagureTest):
         self.session.commit()
 
     def test_lookup_deploykey_non_deploykey(self):
-        """ Test lookup_deploykey with a non-deploykey username. """
+        """Test lookup_deploykey with a non-deploykey username."""
         project = pagure.lib.query._get_project(self.session, "test")
         res = pagure.utils.lookup_deploykey(project, "pingou")
         self.assertEquals(res, None)
 
     def test_lookup_deploykey_different_project(self):
-        """ Test lookup_deploykey with a username for another project. """
+        """Test lookup_deploykey with a username for another project."""
         project = pagure.lib.query._get_project(self.session, "test2")
         res = pagure.utils.lookup_deploykey(project, "deploykey_test_1")
         self.assertEquals(res, None)
 
     def test_lookup_deploykey_non_existent_key(self):
-        """ Test lookup_deploykey with a non-existing deploykey. """
+        """Test lookup_deploykey with a non-existing deploykey."""
         project = pagure.lib.query._get_project(self.session, "test")
         res = pagure.utils.lookup_deploykey(project, "deploykey_test_2")
         self.assertEquals(res, None)
 
     def test_lookup_deploykey(self):
-        """ Test lookup_deploykey with a correct username. """
+        """Test lookup_deploykey with a correct username."""
         project = pagure.lib.query._get_project(self.session, "test")
         res = pagure.utils.lookup_deploykey(project, "deploykey_test_1")
         self.assertNotEquals(res, None)
diff --git a/tests/test_style.py b/tests/test_style.py
index 5c9b81c..91b375a 100644
--- a/tests/test_style.py
+++ b/tests/test_style.py
@@ -34,14 +34,22 @@ class TestStyle(unittest.TestCase):
 
         This test runs flake8 on the code, and will fail if it returns a
         non-zero exit code.
+        If flake8 is not installed, this test auto-skips.
         """
+        try:
+            import flake8
+        except ImportError as e:
+            raise unittest.SkipTest(
+                "flake8 is not installed, skipping flake8 style check..."
+            )
         # We ignore E712, which disallows non-identity comparisons with True and False
         # We ignore W503, which disallows line break before binary operator
         flake8_command = [
             sys.executable,
             "-m",
             "flake8",
-            "--ignore=E712,W503,E203,E902",
+            "--ignore=E712,W503,E203,E902,I201,I100",
+            "--max-line-length=80",
             REPO_PATH,
         ]
 
@@ -76,7 +84,14 @@ class TestStyle(unittest.TestCase):
 
         This test runs black on the code, and will fail if it returns a
         non-zero exit code.
+        If black is not installed, this test auto-skips.
         """
+        try:
+            import black
+        except ImportError as e:
+            raise unittest.SkipTest(
+                "black is not installed, skipping black style check..."
+            )
         black_command = [
             sys.executable,
             "-m",
@@ -86,7 +101,7 @@ class TestStyle(unittest.TestCase):
             "--check",
             "--diff",
             "--exclude",
-            '"/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|_build|buck-out|build|dist)/"',
+            '"/(.eggs|.git|.hg|.mypy_cache|.nox|.tox|.venv|_build|buck-out|build|dist)/"',
             REPO_PATH,
             TESTS_PATH,
         ]
@@ -104,6 +119,49 @@ class TestStyle(unittest.TestCase):
 
         self.assertEqual(proc.returncode, 0)
 
+    def test_code_with_isort(self):
+        """Enforce isort compliance on the codebase.
+
+        This test runs isort on the code, and will fail if it returns a
+        non-zero exit code.
+        If isort is not installed, this test auto-skips.
+        """
+        try:
+            import isort
+        except ImportError as e:
+            raise unittest.SkipTest(
+                "isort is not installed, skipping isort style check..."
+            )
+        # We ignore the hooks files that have a bunch of symlink
+        isort_command = [
+            sys.executable,
+            "-m",
+            "isort",
+            "-v",
+            "--profile",
+            "black",
+            "-s",
+            os.path.join(REPO_PATH, "hooks/files"),
+            "-l",
+            "79",
+            REPO_PATH,
+        ]
+
+        # check if we have an old isort or not
+        import isort
+
+        print(" ".join(isort_command))
+        proc = subprocess.Popen(
+            isort_command, stdout=subprocess.PIPE, cwd=REPO_PATH
+        )
+        stdout, stderr = proc.communicate()
+        print("stdout: ")
+        print(stdout.decode("utf-8")) if stdout else ""
+        print("stderr: ")
+        print(stderr.decode("utf-8")) if stderr else ""
+
+        self.assertEqual(proc.returncode, 0)
+
 
 if __name__ == "__main__":
     unittest.main(verbosity=2)

Debdiff

Debdiff is too long (more than 200 lines). Download the raw debdiff.

More details

Full run details