changeset 8721:67e5b90801aa

lib: move webhelpers2 and friends to webutils Gives less of the unfortunate use of helpers - especially in low level libs.
author Mads Kiilerich <mads@kiilerich.com>
date Sun, 01 Nov 2020 04:59:46 +0100
parents 0c65a8f15e54
children 5dfb757197c9
files kallithea/controllers/admin/auth_settings.py kallithea/controllers/admin/defaults.py kallithea/controllers/admin/gists.py kallithea/controllers/admin/my_account.py kallithea/controllers/admin/permissions.py kallithea/controllers/admin/repo_groups.py kallithea/controllers/admin/repos.py kallithea/controllers/admin/settings.py kallithea/controllers/admin/user_groups.py kallithea/controllers/admin/users.py kallithea/controllers/changelog.py kallithea/controllers/changeset.py kallithea/controllers/compare.py kallithea/controllers/files.py kallithea/controllers/forks.py kallithea/controllers/login.py kallithea/controllers/pullrequests.py kallithea/controllers/summary.py kallithea/lib/auth.py kallithea/lib/base.py kallithea/lib/diffs.py kallithea/lib/helpers.py kallithea/lib/hooks.py kallithea/lib/page.py kallithea/lib/webutils.py kallithea/model/comment.py kallithea/model/db.py kallithea/model/pull_request.py kallithea/model/user.py kallithea/tests/functional/test_admin_users.py kallithea/tests/functional/test_login.py kallithea/tests/functional/test_my_account.py
diffstat 32 files changed, 434 insertions(+), 405 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/controllers/admin/auth_settings.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/admin/auth_settings.py	Sun Nov 01 04:59:46 2020 +0100
@@ -32,8 +32,7 @@
 from tg.i18n import ugettext as _
 from webob.exc import HTTPFound
 
-from kallithea.lib import auth_modules
-from kallithea.lib import helpers as h
+from kallithea.lib import auth_modules, webutils
 from kallithea.lib.auth import HasPermissionAnyDecorator, LoginRequired
 from kallithea.lib.base import BaseController, render
 from kallithea.lib.webutils import url
@@ -132,7 +131,7 @@
                 log.debug("%s = %s", k, str(v))
                 setting = db.Setting.create_or_update(k, v)
             meta.Session().commit()
-            h.flash(_('Auth settings updated successfully'),
+            webutils.flash(_('Auth settings updated successfully'),
                        category='success')
         except formencode.Invalid as errors:
             log.error(traceback.format_exc())
@@ -143,7 +142,7 @@
             )
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('error occurred during update of auth settings'),
+            webutils.flash(_('error occurred during update of auth settings'),
                     category='error')
 
         raise HTTPFound(location=url('auth_home'))
--- a/kallithea/controllers/admin/defaults.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/admin/defaults.py	Sun Nov 01 04:59:46 2020 +0100
@@ -34,7 +34,7 @@
 from tg.i18n import ugettext as _
 from webob.exc import HTTPFound
 
-from kallithea.lib import helpers as h
+from kallithea.lib import webutils
 from kallithea.lib.auth import HasPermissionAnyDecorator, LoginRequired
 from kallithea.lib.base import BaseController, render
 from kallithea.lib.webutils import url
@@ -70,7 +70,7 @@
             for k, v in form_result.items():
                 setting = db.Setting.create_or_update(k, v)
             meta.Session().commit()
-            h.flash(_('Default settings updated successfully'),
+            webutils.flash(_('Default settings updated successfully'),
                     category='success')
 
         except formencode.Invalid as errors:
@@ -85,7 +85,7 @@
                 force_defaults=False)
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during update of defaults'),
+            webutils.flash(_('Error occurred during update of defaults'),
                     category='error')
 
         raise HTTPFound(location=url('defaults'))
--- a/kallithea/controllers/admin/gists.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/admin/gists.py	Sun Nov 01 04:59:46 2020 +0100
@@ -35,8 +35,7 @@
 from tg.i18n import ugettext as _
 from webob.exc import HTTPForbidden, HTTPFound, HTTPNotFound
 
-from kallithea.lib import auth
-from kallithea.lib import helpers as h
+from kallithea.lib import auth, webutils
 from kallithea.lib.auth import LoginRequired
 from kallithea.lib.base import BaseController, jsonify, render
 from kallithea.lib.page import Page
@@ -144,7 +143,7 @@
 
         except Exception as e:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during gist creation'), category='error')
+            webutils.flash(_('Error occurred during gist creation'), category='error')
             raise HTTPFound(location=url('new_gist'))
         raise HTTPFound(location=url('gist', gist_id=new_gist_id))
 
@@ -160,7 +159,7 @@
         if auth.HasPermissionAny('hg.admin')() or owner:
             GistModel().delete(gist)
             meta.Session().commit()
-            h.flash(_('Deleted gist %s') % gist.gist_access_id, category='success')
+            webutils.flash(_('Deleted gist %s') % gist.gist_access_id, category='success')
         else:
             raise HTTPForbidden()
 
@@ -233,15 +232,15 @@
                 )
 
                 meta.Session().commit()
-                h.flash(_('Successfully updated gist content'), category='success')
+                webutils.flash(_('Successfully updated gist content'), category='success')
             except NodeNotChangedError:
                 # raised if nothing was changed in repo itself. We anyway then
                 # store only DB stuff for gist
                 meta.Session().commit()
-                h.flash(_('Successfully updated gist data'), category='success')
+                webutils.flash(_('Successfully updated gist data'), category='success')
             except Exception:
                 log.error(traceback.format_exc())
-                h.flash(_('Error occurred during update of gist %s') % gist_id,
+                webutils.flash(_('Error occurred during update of gist %s') % gist_id,
                         category='error')
 
             raise HTTPFound(location=url('gist', gist_id=gist_id))
--- a/kallithea/controllers/admin/my_account.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/admin/my_account.py	Sun Nov 01 04:59:46 2020 +0100
@@ -35,8 +35,7 @@
 from tg.i18n import ugettext as _
 from webob.exc import HTTPFound
 
-from kallithea.lib import auth_modules
-from kallithea.lib import helpers as h
+from kallithea.lib import auth_modules, webutils
 from kallithea.lib.auth import AuthUser, LoginRequired
 from kallithea.lib.base import BaseController, IfSshEnabled, render
 from kallithea.lib.utils2 import generate_api_key, safe_int
@@ -61,7 +60,7 @@
     def __load_data(self):
         c.user = db.User.get(request.authuser.user_id)
         if c.user.is_default_user:
-            h.flash(_("You can't edit this user since it's"
+            webutils.flash(_("You can't edit this user since it's"
                       " crucial for entire application"), category='warning')
             raise HTTPFound(location=url('users'))
 
@@ -110,7 +109,7 @@
 
                 UserModel().update(request.authuser.user_id, form_result,
                                    skip_attrs=skip_attrs)
-                h.flash(_('Your account was updated successfully'),
+                webutils.flash(_('Your account was updated successfully'),
                         category='success')
                 meta.Session().commit()
                 update = True
@@ -125,7 +124,7 @@
                     force_defaults=False)
             except Exception:
                 log.error(traceback.format_exc())
-                h.flash(_('Error occurred during update of user %s')
+                webutils.flash(_('Error occurred during update of user %s')
                         % form_result.get('username'), category='error')
         if update:
             raise HTTPFound(location='my_account')
@@ -148,7 +147,7 @@
                 form_result = _form.to_python(request.POST)
                 UserModel().update(request.authuser.user_id, form_result)
                 meta.Session().commit()
-                h.flash(_("Successfully updated password"), category='success')
+                webutils.flash(_("Successfully updated password"), category='success')
             except formencode.Invalid as errors:
                 return htmlfill.render(
                     render('admin/my_account/my_account.html'),
@@ -159,7 +158,7 @@
                     force_defaults=False)
             except Exception:
                 log.error(traceback.format_exc())
-                h.flash(_('Error occurred during update of user password'),
+                webutils.flash(_('Error occurred during update of user password'),
                         category='error')
         return render('admin/my_account/my_account.html')
 
@@ -200,13 +199,13 @@
         try:
             UserModel().add_extra_email(request.authuser.user_id, email)
             meta.Session().commit()
-            h.flash(_("Added email %s to user") % email, category='success')
+            webutils.flash(_("Added email %s to user") % email, category='success')
         except formencode.Invalid as error:
             msg = error.error_dict['email']
-            h.flash(msg, category='error')
+            webutils.flash(msg, category='error')
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during email saving'),
+            webutils.flash(_('An error occurred during email saving'),
                     category='error')
         raise HTTPFound(location=url('my_account_emails'))
 
@@ -215,7 +214,7 @@
         user_model = UserModel()
         user_model.delete_extra_email(request.authuser.user_id, email_id)
         meta.Session().commit()
-        h.flash(_("Removed email from user"), category='success')
+        webutils.flash(_("Removed email from user"), category='success')
         raise HTTPFound(location=url('my_account_emails'))
 
     def my_account_api_keys(self):
@@ -239,7 +238,7 @@
         description = request.POST.get('description')
         ApiKeyModel().create(request.authuser.user_id, description, lifetime)
         meta.Session().commit()
-        h.flash(_("API key successfully created"), category='success')
+        webutils.flash(_("API key successfully created"), category='success')
         raise HTTPFound(location=url('my_account_api_keys'))
 
     def my_account_api_keys_delete(self):
@@ -248,11 +247,11 @@
             user = db.User.get(request.authuser.user_id)
             user.api_key = generate_api_key()
             meta.Session().commit()
-            h.flash(_("API key successfully reset"), category='success')
+            webutils.flash(_("API key successfully reset"), category='success')
         elif api_key:
             ApiKeyModel().delete(api_key, request.authuser.user_id)
             meta.Session().commit()
-            h.flash(_("API key successfully deleted"), category='success')
+            webutils.flash(_("API key successfully deleted"), category='success')
 
         raise HTTPFound(location=url('my_account_api_keys'))
 
@@ -272,9 +271,9 @@
                                                description, public_key)
             meta.Session().commit()
             SshKeyModel().write_authorized_keys()
-            h.flash(_("SSH key %s successfully added") % new_ssh_key.fingerprint, category='success')
+            webutils.flash(_("SSH key %s successfully added") % new_ssh_key.fingerprint, category='success')
         except SshKeyModelException as e:
-            h.flash(e.args[0], category='error')
+            webutils.flash(e.args[0], category='error')
         raise HTTPFound(location=url('my_account_ssh_keys'))
 
     @IfSshEnabled
@@ -284,7 +283,7 @@
             SshKeyModel().delete(fingerprint, request.authuser.user_id)
             meta.Session().commit()
             SshKeyModel().write_authorized_keys()
-            h.flash(_("SSH key successfully deleted"), category='success')
+            webutils.flash(_("SSH key successfully deleted"), category='success')
         except SshKeyModelException as e:
-            h.flash(e.args[0], category='error')
+            webutils.flash(e.args[0], category='error')
         raise HTTPFound(location=url('my_account_ssh_keys'))
--- a/kallithea/controllers/admin/permissions.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/admin/permissions.py	Sun Nov 01 04:59:46 2020 +0100
@@ -36,7 +36,7 @@
 from tg.i18n import ugettext as _
 from webob.exc import HTTPFound
 
-from kallithea.lib import helpers as h
+from kallithea.lib import webutils
 from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator, LoginRequired
 from kallithea.lib.base import BaseController, render
 from kallithea.lib.webutils import url
@@ -113,7 +113,7 @@
                 form_result.update({'perm_user_name': 'default'})
                 PermissionModel().update(form_result)
                 meta.Session().commit()
-                h.flash(_('Global permissions updated successfully'),
+                webutils.flash(_('Global permissions updated successfully'),
                         category='success')
 
             except formencode.Invalid as errors:
@@ -128,7 +128,7 @@
                     force_defaults=False)
             except Exception:
                 log.error(traceback.format_exc())
-                h.flash(_('Error occurred during update of permissions'),
+                webutils.flash(_('Error occurred during update of permissions'),
                         category='error')
 
             raise HTTPFound(location=url('admin_permissions'))
--- a/kallithea/controllers/admin/repo_groups.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/admin/repo_groups.py	Sun Nov 01 04:59:46 2020 +0100
@@ -37,6 +37,7 @@
 from webob.exc import HTTPForbidden, HTTPFound, HTTPInternalServerError, HTTPNotFound
 
 from kallithea.lib import helpers as h
+from kallithea.lib import webutils
 from kallithea.lib.auth import HasPermissionAny, HasRepoGroupPermissionLevel, HasRepoGroupPermissionLevelDecorator, LoginRequired
 from kallithea.lib.base import BaseController, render
 from kallithea.lib.utils2 import safe_int
@@ -118,7 +119,7 @@
             repo_groups_data.append({
                 "raw_name": repo_gr.group_name,
                 "group_name": repo_group_name(repo_gr.group_name, children_groups),
-                "desc": h.escape(repo_gr.group_description),
+                "desc": webutils.escape(repo_gr.group_description),
                 "repos": repo_count,
                 "owner": h.person(repo_gr.owner),
                 "action": repo_group_actions(repo_gr.group_id, repo_gr.group_name,
@@ -161,14 +162,14 @@
                 force_defaults=False)
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during creation of repository group %s')
+            webutils.flash(_('Error occurred during creation of repository group %s')
                     % request.POST.get('group_name'), category='error')
             if form_result is None:
                 raise
             parent_group_id = form_result['parent_group_id']
             # TODO: maybe we should get back to the main view, not the admin one
             raise HTTPFound(location=url('repos_groups', parent_group=parent_group_id))
-        h.flash(_('Created repository group %s') % gr.group_name,
+        webutils.flash(_('Created repository group %s') % gr.group_name,
                 category='success')
         raise HTTPFound(location=url('repos_group_home', group_name=gr.group_name))
 
@@ -215,7 +216,7 @@
 
             new_gr = RepoGroupModel().update(group_name, form_result)
             meta.Session().commit()
-            h.flash(_('Updated repository group %s')
+            webutils.flash(_('Updated repository group %s')
                     % form_result['group_name'], category='success')
             # we now have new name !
             group_name = new_gr.group_name
@@ -231,7 +232,7 @@
                 force_defaults=False)
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during update of repository group %s')
+            webutils.flash(_('Error occurred during update of repository group %s')
                     % request.POST.get('group_name'), category='error')
 
         raise HTTPFound(location=url('edit_repo_group', group_name=group_name))
@@ -241,25 +242,25 @@
         gr = c.repo_group = db.RepoGroup.guess_instance(group_name)
         repos = gr.repositories.all()
         if repos:
-            h.flash(_('This group contains %s repositories and cannot be '
+            webutils.flash(_('This group contains %s repositories and cannot be '
                       'deleted') % len(repos), category='warning')
             raise HTTPFound(location=url('repos_groups'))
 
         children = gr.children.all()
         if children:
-            h.flash(_('This group contains %s subgroups and cannot be deleted'
+            webutils.flash(_('This group contains %s subgroups and cannot be deleted'
                       % (len(children))), category='warning')
             raise HTTPFound(location=url('repos_groups'))
 
         try:
             RepoGroupModel().delete(group_name)
             meta.Session().commit()
-            h.flash(_('Removed repository group %s') % group_name,
+            webutils.flash(_('Removed repository group %s') % group_name,
                     category='success')
             # TODO: in future action_logger(, '', '', '')
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during deletion of repository group %s')
+            webutils.flash(_('Error occurred during deletion of repository group %s')
                     % group_name, category='error')
 
         if gr.parent_group:
@@ -344,7 +345,7 @@
         if not request.authuser.is_admin:
             if self._revoke_perms_on_yourself(form_result):
                 msg = _('Cannot revoke permission for yourself as admin')
-                h.flash(msg, category='warning')
+                webutils.flash(msg, category='warning')
                 raise HTTPFound(location=url('edit_repo_group_perms', group_name=group_name))
         recursive = form_result['recursive']
         # iterate over all members(if in recursive mode) of this groups and
@@ -358,7 +359,7 @@
         #action_logger(request.authuser, 'admin_changed_repo_permissions',
         #              repo_name, request.ip_addr)
         meta.Session().commit()
-        h.flash(_('Repository group permissions updated'), category='success')
+        webutils.flash(_('Repository group permissions updated'), category='success')
         raise HTTPFound(location=url('edit_repo_group_perms', group_name=group_name))
 
     @HasRepoGroupPermissionLevelDecorator('admin')
@@ -374,7 +375,7 @@
             if not request.authuser.is_admin:
                 if obj_type == 'user' and request.authuser.user_id == obj_id:
                     msg = _('Cannot revoke permission for yourself as admin')
-                    h.flash(msg, category='warning')
+                    webutils.flash(msg, category='warning')
                     raise Exception('revoke admin permission on self')
             recursive = request.POST.get('recursive', 'none')
             if obj_type == 'user':
@@ -390,6 +391,6 @@
             meta.Session().commit()
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during revoking of permission'),
+            webutils.flash(_('An error occurred during revoking of permission'),
                     category='error')
             raise HTTPInternalServerError()
--- a/kallithea/controllers/admin/repos.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/admin/repos.py	Sun Nov 01 04:59:46 2020 +0100
@@ -37,7 +37,6 @@
 from webob.exc import HTTPForbidden, HTTPFound, HTTPInternalServerError, HTTPNotFound
 
 import kallithea
-from kallithea.lib import helpers as h
 from kallithea.lib import webutils
 from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired, NotAnonymous
 from kallithea.lib.base import BaseRepoController, jsonify, render
@@ -125,7 +124,7 @@
             log.error(traceback.format_exc())
             msg = (_('Error creating repository %s')
                    % form_result.get('repo_name'))
-            h.flash(msg, category='error')
+            webutils.flash(msg, category='error')
             raise HTTPFound(location=url('home'))
 
         raise HTTPFound(location=webutils.url('repo_creating_home',
@@ -179,19 +178,19 @@
         repo = db.Repository.get_by_repo_name(repo_name)
         if repo and repo.repo_state == db.Repository.STATE_CREATED:
             if repo.clone_uri:
-                h.flash(_('Created repository %s from %s')
+                webutils.flash(_('Created repository %s from %s')
                         % (repo.repo_name, repo.clone_uri_hidden), category='success')
             else:
-                repo_url = h.link_to(repo.repo_name,
+                repo_url = webutils.link_to(repo.repo_name,
                                      webutils.url('summary_home',
                                            repo_name=repo.repo_name))
                 fork = repo.fork
                 if fork is not None:
                     fork_name = fork.repo_name
-                    h.flash(h.HTML(_('Forked repository %s as %s'))
+                    webutils.flash(webutils.HTML(_('Forked repository %s as %s'))
                             % (fork_name, repo_url), category='success')
                 else:
-                    h.flash(h.HTML(_('Created repository %s')) % repo_url,
+                    webutils.flash(webutils.HTML(_('Created repository %s')) % repo_url,
                             category='success')
             return {'result': True}
         return {'result': False}
@@ -220,7 +219,7 @@
             form_result = _form.to_python(dict(request.POST))
             repo = repo_model.update(repo_name, **form_result)
             ScmModel().mark_for_invalidation(repo_name)
-            h.flash(_('Repository %s updated successfully') % repo_name,
+            webutils.flash(_('Repository %s updated successfully') % repo_name,
                     category='success')
             changed_name = repo.repo_name
             action_logger(request.authuser, 'admin_updated_repo',
@@ -240,7 +239,7 @@
 
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during update of repository %s')
+            webutils.flash(_('Error occurred during update of repository %s')
                     % repo_name, category='error')
         raise HTTPFound(location=url('edit_repo', repo_name=changed_name))
 
@@ -257,23 +256,23 @@
                 do = request.POST['forks']
                 if do == 'detach_forks':
                     handle_forks = 'detach'
-                    h.flash(_('Detached %s forks') % _forks, category='success')
+                    webutils.flash(_('Detached %s forks') % _forks, category='success')
                 elif do == 'delete_forks':
                     handle_forks = 'delete'
-                    h.flash(_('Deleted %s forks') % _forks, category='success')
+                    webutils.flash(_('Deleted %s forks') % _forks, category='success')
             repo_model.delete(repo, forks=handle_forks)
             action_logger(request.authuser, 'admin_deleted_repo',
                 repo_name, request.ip_addr)
             ScmModel().mark_for_invalidation(repo_name)
-            h.flash(_('Deleted repository %s') % repo_name, category='success')
+            webutils.flash(_('Deleted repository %s') % repo_name, category='success')
             meta.Session().commit()
         except AttachedForksError:
-            h.flash(_('Cannot delete repository %s which still has forks')
+            webutils.flash(_('Cannot delete repository %s which still has forks')
                         % repo_name, category='warning')
 
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during deletion of %s') % repo_name,
+            webutils.flash(_('An error occurred during deletion of %s') % repo_name,
                     category='error')
 
         if repo.group:
@@ -313,7 +312,7 @@
         #action_logger(request.authuser, 'admin_changed_repo_permissions',
         #              repo_name, request.ip_addr)
         meta.Session().commit()
-        h.flash(_('Repository permissions updated'), category='success')
+        webutils.flash(_('Repository permissions updated'), category='success')
         raise HTTPFound(location=url('edit_repo_perms', repo_name=repo_name))
 
     @HasRepoPermissionLevelDecorator('admin')
@@ -342,7 +341,7 @@
             meta.Session().commit()
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during revoking of permission'),
+            webutils.flash(_('An error occurred during revoking of permission'),
                     category='error')
             raise HTTPInternalServerError()
         return []
@@ -372,10 +371,10 @@
             meta.Session().add(new_field)
             meta.Session().commit()
         except formencode.Invalid as e:
-            h.flash(_('Field validation error: %s') % e.msg, category='error')
+            webutils.flash(_('Field validation error: %s') % e.msg, category='error')
         except Exception as e:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during creation of field: %r') % e, category='error')
+            webutils.flash(_('An error occurred during creation of field: %r') % e, category='error')
         raise HTTPFound(location=url('edit_repo_fields', repo_name=repo_name))
 
     @HasRepoPermissionLevelDecorator('admin')
@@ -387,7 +386,7 @@
         except Exception as e:
             log.error(traceback.format_exc())
             msg = _('An error occurred during removal of field')
-            h.flash(msg, category='error')
+            webutils.flash(msg, category='error')
         raise HTTPFound(location=url('edit_repo_fields', repo_name=repo_name))
 
     @HasRepoPermissionLevelDecorator('admin')
@@ -432,11 +431,11 @@
             repo_id = db.Repository.get_by_repo_name(repo_name).repo_id
             user_id = kallithea.DEFAULT_USER_ID
             self.scm_model.toggle_following_repo(repo_id, user_id)
-            h.flash(_('Updated repository visibility in public journal'),
+            webutils.flash(_('Updated repository visibility in public journal'),
                     category='success')
             meta.Session().commit()
         except Exception:
-            h.flash(_('An error occurred during setting this'
+            webutils.flash(_('An error occurred during setting this'
                       ' repository in public journal'),
                     category='error')
         raise HTTPFound(location=url('edit_repo_advanced', repo_name=repo_name))
@@ -454,14 +453,14 @@
                                            request.authuser.username)
             fork = repo.fork.repo_name if repo.fork else _('Nothing')
             meta.Session().commit()
-            h.flash(_('Marked repository %s as fork of %s') % (repo_name, fork),
+            webutils.flash(_('Marked repository %s as fork of %s') % (repo_name, fork),
                     category='success')
         except RepositoryError as e:
             log.error(traceback.format_exc())
-            h.flash(e, category='error')
+            webutils.flash(e, category='error')
         except Exception as e:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during this operation'),
+            webutils.flash(_('An error occurred during this operation'),
                     category='error')
 
         raise HTTPFound(location=url('edit_repo_advanced', repo_name=repo_name))
@@ -473,10 +472,10 @@
         if request.POST:
             try:
                 ScmModel().pull_changes(repo_name, request.authuser.username, request.ip_addr)
-                h.flash(_('Pulled from remote location'), category='success')
+                webutils.flash(_('Pulled from remote location'), category='success')
             except Exception as e:
                 log.error(traceback.format_exc())
-                h.flash(_('An error occurred during pull from remote location'),
+                webutils.flash(_('An error occurred during pull from remote location'),
                         category='error')
             raise HTTPFound(location=url('edit_repo_remote', repo_name=c.repo_name))
         return render('admin/repos/repo_edit.html')
@@ -507,7 +506,7 @@
                 meta.Session().commit()
             except Exception as e:
                 log.error(traceback.format_exc())
-                h.flash(_('An error occurred during deletion of repository stats'),
+                webutils.flash(_('An error occurred during deletion of repository stats'),
                         category='error')
             raise HTTPFound(location=url('edit_repo_statistics', repo_name=c.repo_name))
 
--- a/kallithea/controllers/admin/settings.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/admin/settings.py	Sun Nov 01 04:59:46 2020 +0100
@@ -35,7 +35,6 @@
 from tg.i18n import ugettext as _
 from webob.exc import HTTPFound
 
-from kallithea.lib import helpers as h
 from kallithea.lib import webutils
 from kallithea.lib.auth import HasPermissionAnyDecorator, LoginRequired
 from kallithea.lib.base import BaseController, render
@@ -114,11 +113,11 @@
 
                 meta.Session().commit()
 
-                h.flash(_('Updated VCS settings'), category='success')
+                webutils.flash(_('Updated VCS settings'), category='success')
 
             except Exception:
                 log.error(traceback.format_exc())
-                h.flash(_('Error occurred while updating '
+                webutils.flash(_('Error occurred while updating '
                           'application settings'), category='error')
 
         defaults = db.Setting.get_app_settings()
@@ -147,13 +146,13 @@
                                             install_git_hooks=install_git_hooks,
                                             user=request.authuser.username,
                                             overwrite_git_hooks=overwrite_git_hooks)
-            added_msg = h.HTML(', ').join(
-                h.link_to(safe_str(repo_name), webutils.url('summary_home', repo_name=repo_name)) for repo_name in added
+            added_msg = webutils.HTML(', ').join(
+                webutils.link_to(safe_str(repo_name), webutils.url('summary_home', repo_name=repo_name)) for repo_name in added
             ) or '-'
-            removed_msg = h.HTML(', ').join(
+            removed_msg = webutils.HTML(', ').join(
                 safe_str(repo_name) for repo_name in removed
             ) or '-'
-            h.flash(h.HTML(_('Repositories successfully rescanned. Added: %s. Removed: %s.')) %
+            webutils.flash(webutils.HTML(_('Repositories successfully rescanned. Added: %s. Removed: %s.')) %
                     (added_msg, removed_msg), category='success')
 
             if invalidate_cache:
@@ -165,7 +164,7 @@
                         i += 1
                     except VCSError as e:
                         log.warning('VCS error invalidating %s: %s', repo.repo_name, e)
-                h.flash(_('Invalidated %s repositories') % i, category='success')
+                webutils.flash(_('Invalidated %s repositories') % i, category='success')
 
             raise HTTPFound(location=url('admin_settings_mapping'))
 
@@ -206,11 +205,11 @@
 
                 meta.Session().commit()
                 set_app_settings(config)
-                h.flash(_('Updated application settings'), category='success')
+                webutils.flash(_('Updated application settings'), category='success')
 
             except Exception:
                 log.error(traceback.format_exc())
-                h.flash(_('Error occurred while updating '
+                webutils.flash(_('Error occurred while updating '
                           'application settings'),
                           category='error')
 
@@ -260,12 +259,12 @@
 
                 meta.Session().commit()
                 set_app_settings(config)
-                h.flash(_('Updated visualisation settings'),
+                webutils.flash(_('Updated visualisation settings'),
                         category='success')
 
             except Exception:
                 log.error(traceback.format_exc())
-                h.flash(_('Error occurred during updating '
+                webutils.flash(_('Error occurred during updating '
                           'visualisation settings'),
                         category='error')
 
@@ -289,7 +288,7 @@
             test_body = ('Kallithea Email test, '
                                'Kallithea version: %s' % c.kallithea_version)
             if not test_email:
-                h.flash(_('Please enter email address'), category='error')
+                webutils.flash(_('Please enter email address'), category='error')
                 raise HTTPFound(location=url('admin_settings_email'))
 
             test_email_txt_body = EmailNotificationModel() \
@@ -304,7 +303,7 @@
             tasks.send_email(recipients, test_email_subj,
                              test_email_txt_body, test_email_html_body)
 
-            h.flash(_('Send email task created'), category='success')
+            webutils.flash(_('Send email task created'), category='success')
             raise HTTPFound(location=url('admin_settings_email'))
 
         defaults = db.Setting.get_app_settings()
@@ -332,12 +331,12 @@
                 try:
                     ui_key = ui_key and ui_key.strip()
                     if ui_key in (x.ui_key for x in db.Ui.get_custom_hooks()):
-                        h.flash(_('Hook already exists'), category='error')
+                        webutils.flash(_('Hook already exists'), category='error')
                     elif ui_key in (x.ui_key for x in db.Ui.get_builtin_hooks()):
-                        h.flash(_('Builtin hooks are read-only. Please use another hook name.'), category='error')
+                        webutils.flash(_('Builtin hooks are read-only. Please use another hook name.'), category='error')
                     elif ui_value and ui_key:
                         db.Ui.create_or_update_hook(ui_key, ui_value)
-                        h.flash(_('Added new hook'), category='success')
+                        webutils.flash(_('Added new hook'), category='success')
                     elif hook_id:
                         db.Ui.delete(hook_id)
                         meta.Session().commit()
@@ -353,11 +352,11 @@
                             update = True
 
                     if update:
-                        h.flash(_('Updated hooks'), category='success')
+                        webutils.flash(_('Updated hooks'), category='success')
                     meta.Session().commit()
                 except Exception:
                     log.error(traceback.format_exc())
-                    h.flash(_('Error occurred during hook creation'),
+                    webutils.flash(_('Error occurred during hook creation'),
                             category='error')
 
                 raise HTTPFound(location=url('admin_settings_hooks'))
@@ -381,7 +380,7 @@
             repo_location = self._get_hg_ui_settings()['paths_root_path']
             full_index = request.POST.get('full_index', False)
             tasks.whoosh_index(repo_location, full_index)
-            h.flash(_('Whoosh reindex task scheduled'), category='success')
+            webutils.flash(_('Whoosh reindex task scheduled'), category='success')
             raise HTTPFound(location=url('admin_settings_search'))
 
         defaults = db.Setting.get_app_settings()
--- a/kallithea/controllers/admin/user_groups.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/admin/user_groups.py	Sun Nov 01 04:59:46 2020 +0100
@@ -38,6 +38,7 @@
 from webob.exc import HTTPFound, HTTPInternalServerError
 
 from kallithea.lib import helpers as h
+from kallithea.lib import webutils
 from kallithea.lib.auth import HasPermissionAnyDecorator, HasUserGroupPermissionLevelDecorator, LoginRequired
 from kallithea.lib.base import BaseController, render
 from kallithea.lib.exceptions import RepoGroupAssignmentError, UserGroupsAssignedException
@@ -100,7 +101,7 @@
                 "raw_name": user_gr.users_group_name,
                 "group_name": user_group_name(user_gr.users_group_id,
                                               user_gr.users_group_name),
-                "desc": h.escape(user_gr.user_group_description),
+                "desc": webutils.escape(user_gr.user_group_description),
                 "members": len(user_gr.members),
                 "active": h.boolicon(user_gr.users_group_active),
                 "owner": h.person(user_gr.owner.username),
@@ -129,7 +130,7 @@
             action_logger(request.authuser,
                           'admin_created_users_group:%s' % gr,
                           None, request.ip_addr)
-            h.flash(h.HTML(_('Created user group %s')) % h.link_to(gr, url('edit_users_group', id=ug.users_group_id)),
+            webutils.flash(webutils.HTML(_('Created user group %s')) % webutils.link_to(gr, url('edit_users_group', id=ug.users_group_id)),
                 category='success')
             meta.Session().commit()
         except formencode.Invalid as errors:
@@ -142,7 +143,7 @@
                 force_defaults=False)
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during creation of user group %s')
+            webutils.flash(_('Error occurred during creation of user group %s')
                     % request.POST.get('users_group_name'), category='error')
 
         raise HTTPFound(location=url('users_groups'))
@@ -170,7 +171,7 @@
             action_logger(request.authuser,
                           'admin_updated_users_group:%s' % gr,
                           None, request.ip_addr)
-            h.flash(_('Updated user group %s') % gr, category='success')
+            webutils.flash(_('Updated user group %s') % gr, category='success')
             meta.Session().commit()
         except formencode.Invalid as errors:
             ug_model = UserGroupModel()
@@ -192,7 +193,7 @@
                 force_defaults=False)
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during update of user group %s')
+            webutils.flash(_('Error occurred during update of user group %s')
                     % request.POST.get('users_group_name'), category='error')
 
         raise HTTPFound(location=url('edit_users_group', id=id))
@@ -203,12 +204,12 @@
         try:
             UserGroupModel().delete(usr_gr)
             meta.Session().commit()
-            h.flash(_('Successfully deleted user group'), category='success')
+            webutils.flash(_('Successfully deleted user group'), category='success')
         except UserGroupsAssignedException as e:
-            h.flash(e, category='error')
+            webutils.flash(e, category='error')
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during deletion of user group'),
+            webutils.flash(_('An error occurred during deletion of user group'),
                     category='error')
         raise HTTPFound(location=url('users_groups'))
 
@@ -264,13 +265,13 @@
             UserGroupModel()._update_permissions(user_group, form['perms_new'],
                                                  form['perms_updates'])
         except RepoGroupAssignmentError:
-            h.flash(_('Target group cannot be the same'), category='error')
+            webutils.flash(_('Target group cannot be the same'), category='error')
             raise HTTPFound(location=url('edit_user_group_perms', id=id))
         # TODO: implement this
         #action_logger(request.authuser, 'admin_changed_repo_permissions',
         #              repo_name, request.ip_addr)
         meta.Session().commit()
-        h.flash(_('User group permissions updated'), category='success')
+        webutils.flash(_('User group permissions updated'), category='success')
         raise HTTPFound(location=url('edit_user_group_perms', id=id))
 
     @HasUserGroupPermissionLevelDecorator('admin')
@@ -286,7 +287,7 @@
             if not request.authuser.is_admin:
                 if obj_type == 'user' and request.authuser.user_id == obj_id:
                     msg = _('Cannot revoke permission for yourself as admin')
-                    h.flash(msg, category='warning')
+                    webutils.flash(msg, category='warning')
                     raise Exception('revoke admin permission on self')
             if obj_type == 'user':
                 UserGroupModel().revoke_user_permission(user_group=id,
@@ -297,7 +298,7 @@
             meta.Session().commit()
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during revoking of permission'),
+            webutils.flash(_('An error occurred during revoking of permission'),
                     category='error')
             raise HTTPInternalServerError()
 
@@ -379,11 +380,11 @@
             else:
                 usergroup_model.grant_perm(id, 'hg.fork.none')
 
-            h.flash(_("Updated permissions"), category='success')
+            webutils.flash(_("Updated permissions"), category='success')
             meta.Session().commit()
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during permissions saving'),
+            webutils.flash(_('An error occurred during permissions saving'),
                     category='error')
 
         raise HTTPFound(location=url('edit_user_group_default_perms', id=id))
--- a/kallithea/controllers/admin/users.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/admin/users.py	Sun Nov 01 04:59:46 2020 +0100
@@ -39,6 +39,7 @@
 import kallithea
 from kallithea.lib import auth_modules
 from kallithea.lib import helpers as h
+from kallithea.lib import webutils
 from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator, LoginRequired
 from kallithea.lib.base import BaseController, IfSshEnabled, render
 from kallithea.lib.exceptions import DefaultUserException, UserCreationError, UserOwnsReposException
@@ -87,8 +88,8 @@
                 "gravatar": grav_tmpl % h.gravatar(user.email, size=20),
                 "raw_name": user.username,
                 "username": username(user.user_id, user.username),
-                "firstname": h.escape(user.name),
-                "lastname": h.escape(user.lastname),
+                "firstname": webutils.escape(user.name),
+                "lastname": webutils.escape(user.lastname),
                 "last_login": h.fmt_date(user.last_login),
                 "last_login_raw": datetime_to_time(user.last_login),
                 "active": h.boolicon(user.active),
@@ -116,7 +117,7 @@
             user = user_model.create(form_result)
             action_logger(request.authuser, 'admin_created_user:%s' % user.username,
                           None, request.ip_addr)
-            h.flash(_('Created user %s') % user.username,
+            webutils.flash(_('Created user %s') % user.username,
                     category='success')
             meta.Session().commit()
         except formencode.Invalid as errors:
@@ -128,10 +129,10 @@
                 encoding="UTF-8",
                 force_defaults=False)
         except UserCreationError as e:
-            h.flash(e, 'error')
+            webutils.flash(e, 'error')
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during creation of user %s')
+            webutils.flash(_('Error occurred during creation of user %s')
                     % request.POST.get('username'), category='error')
         raise HTTPFound(location=url('edit_user', id=user.user_id))
 
@@ -155,7 +156,7 @@
             usr = form_result['username']
             action_logger(request.authuser, 'admin_updated_user:%s' % usr,
                           None, request.ip_addr)
-            h.flash(_('User updated successfully'), category='success')
+            webutils.flash(_('User updated successfully'), category='success')
             meta.Session().commit()
         except formencode.Invalid as errors:
             defaults = errors.value
@@ -174,7 +175,7 @@
                 force_defaults=False)
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during update of user %s')
+            webutils.flash(_('Error occurred during update of user %s')
                     % form_result.get('username'), category='error')
         raise HTTPFound(location=url('edit_user', id=id))
 
@@ -184,12 +185,12 @@
         try:
             UserModel().delete(usr)
             meta.Session().commit()
-            h.flash(_('Successfully deleted user'), category='success')
+            webutils.flash(_('Successfully deleted user'), category='success')
         except (UserOwnsReposException, DefaultUserException) as e:
-            h.flash(e, category='warning')
+            webutils.flash(e, category='warning')
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during deletion of user'),
+            webutils.flash(_('An error occurred during deletion of user'),
                     category='error')
         else:
             if has_ssh_keys:
@@ -200,7 +201,7 @@
         try:
             return db.User.get_or_404(id, allow_default=False)
         except DefaultUserException:
-            h.flash(_("The default user cannot be edited"), category='warning')
+            webutils.flash(_("The default user cannot be edited"), category='warning')
             raise HTTPNotFound
 
     def _render_edit_profile(self, user):
@@ -268,7 +269,7 @@
         description = request.POST.get('description')
         ApiKeyModel().create(c.user.user_id, description, lifetime)
         meta.Session().commit()
-        h.flash(_("API key successfully created"), category='success')
+        webutils.flash(_("API key successfully created"), category='success')
         raise HTTPFound(location=url('edit_user_api_keys', id=c.user.user_id))
 
     def delete_api_key(self, id):
@@ -278,11 +279,11 @@
         if request.POST.get('del_api_key_builtin'):
             c.user.api_key = generate_api_key()
             meta.Session().commit()
-            h.flash(_("API key successfully reset"), category='success')
+            webutils.flash(_("API key successfully reset"), category='success')
         elif api_key:
             ApiKeyModel().delete(api_key, c.user.user_id)
             meta.Session().commit()
-            h.flash(_("API key successfully deleted"), category='success')
+            webutils.flash(_("API key successfully deleted"), category='success')
 
         raise HTTPFound(location=url('edit_user_api_keys', id=c.user.user_id))
 
@@ -335,11 +336,11 @@
                 user_model.grant_perm(id, 'hg.fork.repository')
             else:
                 user_model.grant_perm(id, 'hg.fork.none')
-            h.flash(_("Updated permissions"), category='success')
+            webutils.flash(_("Updated permissions"), category='success')
             meta.Session().commit()
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during permissions saving'),
+            webutils.flash(_('An error occurred during permissions saving'),
                     category='error')
         raise HTTPFound(location=url('edit_user_perms', id=id))
 
@@ -364,13 +365,13 @@
         try:
             user_model.add_extra_email(id, email)
             meta.Session().commit()
-            h.flash(_("Added email %s to user") % email, category='success')
+            webutils.flash(_("Added email %s to user") % email, category='success')
         except formencode.Invalid as error:
             msg = error.error_dict['email']
-            h.flash(msg, category='error')
+            webutils.flash(msg, category='error')
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during email saving'),
+            webutils.flash(_('An error occurred during email saving'),
                     category='error')
         raise HTTPFound(location=url('edit_user_emails', id=id))
 
@@ -380,7 +381,7 @@
         user_model = UserModel()
         user_model.delete_extra_email(id, email_id)
         meta.Session().commit()
-        h.flash(_("Removed email from user"), category='success')
+        webutils.flash(_("Removed email from user"), category='success')
         raise HTTPFound(location=url('edit_user_emails', id=id))
 
     def edit_ips(self, id):
@@ -406,13 +407,13 @@
         try:
             user_model.add_extra_ip(id, ip)
             meta.Session().commit()
-            h.flash(_("Added IP address %s to user whitelist") % ip, category='success')
+            webutils.flash(_("Added IP address %s to user whitelist") % ip, category='success')
         except formencode.Invalid as error:
             msg = error.error_dict['ip']
-            h.flash(msg, category='error')
+            webutils.flash(msg, category='error')
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred while adding IP address'),
+            webutils.flash(_('An error occurred while adding IP address'),
                     category='error')
 
         if 'default_user' in request.POST:
@@ -424,7 +425,7 @@
         user_model = UserModel()
         user_model.delete_extra_ip(id, ip_id)
         meta.Session().commit()
-        h.flash(_("Removed IP address from user whitelist"), category='success')
+        webutils.flash(_("Removed IP address from user whitelist"), category='success')
 
         if 'default_user' in request.POST:
             raise HTTPFound(location=url('admin_permissions_ips'))
@@ -453,9 +454,9 @@
                                                description, public_key)
             meta.Session().commit()
             SshKeyModel().write_authorized_keys()
-            h.flash(_("SSH key %s successfully added") % new_ssh_key.fingerprint, category='success')
+            webutils.flash(_("SSH key %s successfully added") % new_ssh_key.fingerprint, category='success')
         except SshKeyModelException as e:
-            h.flash(e.args[0], category='error')
+            webutils.flash(e.args[0], category='error')
         raise HTTPFound(location=url('edit_user_ssh_keys', id=c.user.user_id))
 
     @IfSshEnabled
@@ -467,7 +468,7 @@
             SshKeyModel().delete(fingerprint, c.user.user_id)
             meta.Session().commit()
             SshKeyModel().write_authorized_keys()
-            h.flash(_("SSH key successfully deleted"), category='success')
+            webutils.flash(_("SSH key successfully deleted"), category='success')
         except SshKeyModelException as e:
-            h.flash(e.args[0], category='error')
+            webutils.flash(e.args[0], category='error')
         raise HTTPFound(location=url('edit_user_ssh_keys', id=c.user.user_id))
--- a/kallithea/controllers/changelog.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/changelog.py	Sun Nov 01 04:59:46 2020 +0100
@@ -33,7 +33,6 @@
 from tg.i18n import ugettext as _
 from webob.exc import HTTPBadRequest, HTTPFound, HTTPNotFound
 
-import kallithea.lib.helpers as h
 from kallithea.lib import webutils
 from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 from kallithea.lib.base import BaseRepoController, render
@@ -65,10 +64,10 @@
         try:
             return c.db_repo_scm_instance.get_changeset(rev)
         except EmptyRepositoryError as e:
-            h.flash(_('There are no changesets yet'), category='error')
+            webutils.flash(_('There are no changesets yet'), category='error')
         except RepositoryError as e:
             log.error(traceback.format_exc())
-            h.flash(e, category='error')
+            webutils.flash(e, category='error')
         raise HTTPBadRequest()
 
     @LoginRequired(allow_default_user=True)
@@ -112,7 +111,7 @@
                         cs = self.__get_cs(revision, repo_name)
                         collection = cs.get_file_history(f_path)
                     except RepositoryError as e:
-                        h.flash(e, category='warning')
+                        webutils.flash(e, category='warning')
                         raise HTTPFound(location=webutils.url('changelog_home', repo_name=repo_name))
             else:
                 collection = c.db_repo_scm_instance.get_changesets(start=0, end=revision,
@@ -126,11 +125,11 @@
             c.cs_comments = c.db_repo.get_comments(page_revisions)
             c.cs_statuses = c.db_repo.statuses(page_revisions)
         except EmptyRepositoryError as e:
-            h.flash(e, category='warning')
+            webutils.flash(e, category='warning')
             raise HTTPFound(location=url('summary_home', repo_name=c.repo_name))
         except (RepositoryError, ChangesetDoesNotExistError, Exception) as e:
             log.error(traceback.format_exc())
-            h.flash(e, category='error')
+            webutils.flash(e, category='error')
             raise HTTPFound(location=url('changelog_home', repo_name=c.repo_name))
 
         c.branch_name = branch_name
--- a/kallithea/controllers/changeset.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/changeset.py	Sun Nov 01 04:59:46 2020 +0100
@@ -86,7 +86,7 @@
 
     if not allowed_to_change_status:
         if status or close_pr:
-            h.flash(_('No permission to change status'), 'error')
+            webutils.flash(_('No permission to change status'), 'error')
             raise HTTPForbidden()
 
     if pull_request and delete == "delete":
@@ -97,7 +97,7 @@
         ) and not pull_request.is_closed():
             PullRequestModel().delete(pull_request)
             meta.Session().commit()
-            h.flash(_('Successfully deleted pull request %s') % pull_request_id,
+            webutils.flash(_('Successfully deleted pull request %s') % pull_request_id,
                     category='success')
             return {
                'location': webutils.url('my_pullrequests'), # or repo pr list?
@@ -143,7 +143,7 @@
     meta.Session().commit()
 
     data = {
-       'target_id': h.safeid(request.POST.get('f_path')),
+       'target_id': webutils.safeid(request.POST.get('f_path')),
     }
     if comment is not None:
         c.comment = comment
@@ -199,7 +199,7 @@
         except (ChangesetDoesNotExistError, EmptyRepositoryError):
             log.debug(traceback.format_exc())
             msg = _('Such revision does not exist for this repository')
-            h.flash(msg, category='error')
+            webutils.flash(msg, category='error')
             raise HTTPNotFound()
 
         c.changes = OrderedDict()
--- a/kallithea/controllers/compare.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/compare.py	Sun Nov 01 04:59:46 2020 +0100
@@ -63,13 +63,13 @@
             c.cs_repo = db.Repository.get_by_repo_name(other_repo)
             if c.cs_repo is None:
                 msg = _('Could not find other repository %s') % other_repo
-                h.flash(msg, category='error')
+                webutils.flash(msg, category='error')
                 raise HTTPFound(location=url('compare_home', repo_name=c.a_repo.repo_name))
 
         # Verify that it's even possible to compare these two repositories.
         if c.a_repo.scm_instance.alias != c.cs_repo.scm_instance.alias:
             msg = _('Cannot compare repositories of different types')
-            h.flash(msg, category='error')
+            webutils.flash(msg, category='error')
             raise HTTPFound(location=url('compare_home', repo_name=c.a_repo.repo_name))
 
     @LoginRequired(allow_default_user=True)
@@ -146,7 +146,7 @@
             else:
                 msg = _('Multiple merge ancestors found for merge compare')
             if rev1 is None:
-                h.flash(msg, category='error')
+                webutils.flash(msg, category='error')
                 log.error(msg)
                 raise HTTPNotFound
 
@@ -160,7 +160,7 @@
             if org_repo != other_repo:
                 # TODO: we could do this by using hg unionrepo
                 log.error('cannot compare across repos %s and %s', org_repo, other_repo)
-                h.flash(_('Cannot compare repositories without using common ancestor'), category='error')
+                webutils.flash(_('Cannot compare repositories without using common ancestor'), category='error')
                 raise HTTPBadRequest
             rev1 = c.a_rev
 
--- a/kallithea/controllers/files.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/files.py	Sun Nov 01 04:59:46 2020 +0100
@@ -83,15 +83,15 @@
             url_ = url('files_add_home',
                        repo_name=c.repo_name,
                        revision=0, f_path='', anchor='edit')
-            add_new = h.link_to(_('Click here to add new file'), url_, class_="alert-link")
-            h.flash(_('There are no files yet.') + ' ' + add_new, category='warning')
+            add_new = webutils.link_to(_('Click here to add new file'), url_, class_="alert-link")
+            webutils.flash(_('There are no files yet.') + ' ' + add_new, category='warning')
             raise HTTPNotFound()
         except (ChangesetDoesNotExistError, LookupError):
             msg = _('Such revision does not exist for this repository')
-            h.flash(msg, category='error')
+            webutils.flash(msg, category='error')
             raise HTTPNotFound()
         except RepositoryError as e:
-            h.flash(e, category='error')
+            webutils.flash(e, category='error')
             raise HTTPNotFound()
 
     def __get_filenode(self, cs, path):
@@ -108,10 +108,10 @@
                 raise RepositoryError('given path is a directory')
         except ChangesetDoesNotExistError:
             msg = _('Such revision does not exist for this repository')
-            h.flash(msg, category='error')
+            webutils.flash(msg, category='error')
             raise HTTPNotFound()
         except RepositoryError as e:
-            h.flash(e, category='error')
+            webutils.flash(e, category='error')
             raise HTTPNotFound()
 
         return file_node
@@ -176,7 +176,7 @@
             else:
                 c.authors = c.file_history = []
         except RepositoryError as e:
-            h.flash(e, category='error')
+            webutils.flash(e, category='error')
             raise HTTPNotFound()
 
         if request.environ.get('HTTP_X_PARTIAL_XHR'):
@@ -293,7 +293,7 @@
         _branches = repo.scm_instance.branches
         # check if revision is a branch name or branch hash
         if revision not in _branches and revision not in _branches.values():
-            h.flash(_('You can only delete files with revision '
+            webutils.flash(_('You can only delete files with revision '
                       'being a valid branch'), category='warning')
             raise HTTPFound(location=webutils.url('files_home',
                                   repo_name=repo_name, revision='tip',
@@ -328,11 +328,11 @@
                     author=author,
                 )
 
-                h.flash(_('Successfully deleted file %s') % f_path,
+                webutils.flash(_('Successfully deleted file %s') % f_path,
                         category='success')
             except Exception:
                 log.error(traceback.format_exc())
-                h.flash(_('Error occurred during commit'), category='error')
+                webutils.flash(_('Error occurred during commit'), category='error')
             raise HTTPFound(location=url('changeset_home',
                                 repo_name=c.repo_name, revision='tip'))
 
@@ -347,7 +347,7 @@
         _branches = repo.scm_instance.branches
         # check if revision is a branch name or branch hash
         if revision not in _branches and revision not in _branches.values():
-            h.flash(_('You can only edit files with revision '
+            webutils.flash(_('You can only edit files with revision '
                       'being a valid branch'), category='warning')
             raise HTTPFound(location=webutils.url('files_home',
                                   repo_name=repo_name, revision='tip',
@@ -376,7 +376,7 @@
             author = request.authuser.full_contact
 
             if content == old_content:
-                h.flash(_('No changes'), category='warning')
+                webutils.flash(_('No changes'), category='warning')
                 raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name,
                                     revision='tip'))
             try:
@@ -386,11 +386,11 @@
                                              ip_addr=request.ip_addr,
                                              author=author, message=message,
                                              content=content, f_path=f_path)
-                h.flash(_('Successfully committed to %s') % f_path,
+                webutils.flash(_('Successfully committed to %s') % f_path,
                         category='success')
             except Exception:
                 log.error(traceback.format_exc())
-                h.flash(_('Error occurred during commit'), category='error')
+                webutils.flash(_('Error occurred during commit'), category='error')
             raise HTTPFound(location=url('changeset_home',
                                 repo_name=c.repo_name, revision='tip'))
 
@@ -426,11 +426,11 @@
                     content = content.file
 
             if not content:
-                h.flash(_('No content'), category='warning')
+                webutils.flash(_('No content'), category='warning')
                 raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name,
                                     revision='tip'))
             if not filename:
-                h.flash(_('No filename'), category='warning')
+                webutils.flash(_('No filename'), category='warning')
                 raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name,
                                     revision='tip'))
             # strip all crap out of file, just leave the basename
@@ -454,18 +454,18 @@
                     author=author,
                 )
 
-                h.flash(_('Successfully committed to %s') % node_path,
+                webutils.flash(_('Successfully committed to %s') % node_path,
                         category='success')
             except NonRelativePathError as e:
-                h.flash(_('Location must be relative path and must not '
+                webutils.flash(_('Location must be relative path and must not '
                           'contain .. in path'), category='warning')
                 raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name,
                                     revision='tip'))
             except (NodeError, NodeAlreadyExistsError) as e:
-                h.flash(_(e), category='error')
+                webutils.flash(_(e), category='error')
             except Exception:
                 log.error(traceback.format_exc())
-                h.flash(_('Error occurred during commit'), category='error')
+                webutils.flash(_('Error occurred during commit'), category='error')
             raise HTTPFound(location=url('changeset_home',
                                 repo_name=c.repo_name, revision='tip'))
 
@@ -687,7 +687,7 @@
                 node2 = FileNode(f_path, '', changeset=c.changeset_2)
         except ChangesetDoesNotExistError as e:
             msg = _('Such revision does not exist for this repository')
-            h.flash(msg, category='error')
+            webutils.flash(msg, category='error')
             raise HTTPNotFound()
         c.node1 = node1
         c.node2 = node2
--- a/kallithea/controllers/forks.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/forks.py	Sun Nov 01 04:59:46 2020 +0100
@@ -36,7 +36,6 @@
 from webob.exc import HTTPFound, HTTPNotFound
 
 import kallithea
-import kallithea.lib.helpers as h
 from kallithea.lib import webutils
 from kallithea.lib.auth import HasPermissionAnyDecorator, HasRepoPermissionLevel, HasRepoPermissionLevelDecorator, LoginRequired
 from kallithea.lib.base import BaseRepoController, render
@@ -166,7 +165,7 @@
                 force_defaults=False)
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('An error occurred during repository forking %s') %
+            webutils.flash(_('An error occurred during repository forking %s') %
                     repo_name, category='error')
 
         raise HTTPFound(location=webutils.url('repo_creating_home',
--- a/kallithea/controllers/login.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/login.py	Sun Nov 01 04:59:46 2020 +0100
@@ -36,7 +36,7 @@
 from tg.i18n import ugettext as _
 from webob.exc import HTTPBadRequest, HTTPFound
 
-import kallithea.lib.helpers as h
+from kallithea.lib import webutils
 from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator
 from kallithea.lib.base import BaseController, log_in_user, render
 from kallithea.lib.exceptions import UserCreationError
@@ -99,13 +99,13 @@
                 # the fly can throw this exception signaling that there's issue
                 # with user creation, explanation should be provided in
                 # Exception itself
-                h.flash(e, 'error')
+                webutils.flash(e, 'error')
             else:
                 # login_form already validated the password - now set the session cookie accordingly
                 auth_user = log_in_user(user, c.form_result['remember'], is_external_auth=False, ip_addr=request.ip_addr)
                 if auth_user:
                     raise HTTPFound(location=c.came_from)
-                h.flash(_('Authentication failed.'), 'error')
+                webutils.flash(_('Authentication failed.'), 'error')
         else:
             # redirect if already logged in
             if not request.authuser.is_anonymous:
@@ -144,7 +144,7 @@
                                                  error_dict=error_dict)
 
                 UserModel().create_registration(form_result)
-                h.flash(_('You have successfully registered with %s') % (c.site_name or 'Kallithea'),
+                webutils.flash(_('You have successfully registered with %s') % (c.site_name or 'Kallithea'),
                         category='success')
                 meta.Session().commit()
                 raise HTTPFound(location=url('login_home'))
@@ -162,7 +162,7 @@
                 # the fly can throw this exception signaling that there's issue
                 # with user creation, explanation should be provided in
                 # Exception itself
-                h.flash(e, 'error')
+                webutils.flash(e, 'error')
 
         return render('/register.html')
 
@@ -188,7 +188,7 @@
                         raise formencode.Invalid(_msg, _value, None,
                                                  error_dict=error_dict)
                 redirect_link = UserModel().send_reset_password_email(form_result)
-                h.flash(_('A password reset confirmation code has been sent'),
+                webutils.flash(_('A password reset confirmation code has been sent'),
                             category='success')
                 raise HTTPFound(location=redirect_link)
 
@@ -240,7 +240,7 @@
                 encoding='UTF-8')
 
         UserModel().reset_password(form_result['email'], form_result['password'])
-        h.flash(_('Successfully updated password'), category='success')
+        webutils.flash(_('Successfully updated password'), category='success')
         raise HTTPFound(location=url('login_home'))
 
     def logout(self):
@@ -254,4 +254,4 @@
         Only intended for testing but might also be useful for other kinds
         of automation.
         """
-        return h.session_csrf_secret_token()
+        return webutils.session_csrf_secret_token()
--- a/kallithea/controllers/pullrequests.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/pullrequests.py	Sun Nov 01 04:59:46 2020 +0100
@@ -35,9 +35,9 @@
 from tg.i18n import ugettext as _
 from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPFound, HTTPNotFound
 
+import kallithea.lib.helpers as h
 from kallithea.controllers.changeset import create_cs_pr_comment, delete_cs_pr_comment
-from kallithea.lib import auth, diffs
-from kallithea.lib import helpers as h
+from kallithea.lib import auth, diffs, webutils
 from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 from kallithea.lib.base import BaseRepoController, jsonify, render
 from kallithea.lib.graphmod import graph_data
@@ -63,7 +63,7 @@
         user = None
 
     if user is None or user.is_default_user:
-        h.flash(_('Invalid reviewer "%s" specified') % user_id, category='error')
+        webutils.flash(_('Invalid reviewer "%s" specified') % user_id, category='error')
         raise HTTPBadRequest()
 
     return user
@@ -245,7 +245,7 @@
         try:
             org_scm_instance.get_changeset()
         except EmptyRepositoryError as e:
-            h.flash(_('There are no changesets yet'),
+            webutils.flash(_('There are no changesets yet'),
                     category='warning')
             raise HTTPFound(location=url('summary_home', repo_name=org_repo.repo_name))
 
@@ -314,7 +314,7 @@
             log.error(traceback.format_exc())
             log.error(str(errors))
             msg = _('Error creating pull request: %s') % errors.msg
-            h.flash(msg, 'error')
+            webutils.flash(msg, 'error')
             raise HTTPBadRequest
 
         # heads up: org and other might seem backward here ...
@@ -333,19 +333,19 @@
         try:
             cmd = CreatePullRequestAction(org_repo, other_repo, org_ref, other_ref, title, description, owner, reviewers)
         except CreatePullRequestAction.ValidationError as e:
-            h.flash(e, category='error', logf=log.error)
+            webutils.flash(e, category='error', logf=log.error)
             raise HTTPNotFound
 
         try:
             pull_request = cmd.execute()
             meta.Session().commit()
         except Exception:
-            h.flash(_('Error occurred while creating pull request'),
+            webutils.flash(_('Error occurred while creating pull request'),
                     category='error')
             log.error(traceback.format_exc())
             raise HTTPFound(location=url('pullrequest_home', repo_name=repo_name))
 
-        h.flash(_('Successfully opened new pull request'),
+        webutils.flash(_('Successfully opened new pull request'),
                 category='success')
         raise HTTPFound(location=pull_request.url())
 
@@ -356,19 +356,19 @@
         try:
             cmd = CreatePullRequestIterationAction(old_pull_request, new_org_rev, new_other_rev, title, description, owner, reviewers)
         except CreatePullRequestAction.ValidationError as e:
-            h.flash(e, category='error', logf=log.error)
+            webutils.flash(e, category='error', logf=log.error)
             raise HTTPNotFound
 
         try:
             pull_request = cmd.execute()
             meta.Session().commit()
         except Exception:
-            h.flash(_('Error occurred while creating pull request'),
+            webutils.flash(_('Error occurred while creating pull request'),
                     category='error')
             log.error(traceback.format_exc())
             raise HTTPFound(location=old_pull_request.url())
 
-        h.flash(_('New pull request iteration created'),
+        webutils.flash(_('New pull request iteration created'),
                 category='success')
         raise HTTPFound(location=pull_request.url())
 
@@ -396,11 +396,11 @@
         other_removed = old_reviewers - cur_reviewers
 
         if other_added:
-            h.flash(_('Meanwhile, the following reviewers have been added: %s') %
+            webutils.flash(_('Meanwhile, the following reviewers have been added: %s') %
                     (', '.join(u.username for u in other_added)),
                     category='warning')
         if other_removed:
-            h.flash(_('Meanwhile, the following reviewers have been removed: %s') %
+            webutils.flash(_('Meanwhile, the following reviewers have been removed: %s') %
                     (', '.join(u.username for u in other_removed)),
                     category='warning')
 
@@ -425,7 +425,7 @@
         PullRequestModel().remove_reviewers(user, pull_request, removed_reviewers)
 
         meta.Session().commit()
-        h.flash(_('Pull request updated'), category='success')
+        webutils.flash(_('Pull request updated'), category='success')
 
         raise HTTPFound(location=pull_request.url())
 
@@ -438,7 +438,7 @@
         if pull_request.owner_id == request.authuser.user_id:
             PullRequestModel().delete(pull_request)
             meta.Session().commit()
-            h.flash(_('Successfully deleted pull request'),
+            webutils.flash(_('Successfully deleted pull request'),
                     category='success')
             raise HTTPFound(location=url('my_pullrequests'))
         raise HTTPForbidden()
@@ -474,7 +474,7 @@
                 c.cs_ranges.append(org_scm_instance.get_changeset(x))
             except ChangesetDoesNotExistError:
                 c.cs_ranges = []
-                h.flash(_('Revision %s not found in %s') % (x, c.cs_repo.repo_name),
+                webutils.flash(_('Revision %s not found in %s') % (x, c.cs_repo.repo_name),
                     'error')
                 break
         c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ...
--- a/kallithea/controllers/summary.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/controllers/summary.py	Sun Nov 01 04:59:46 2020 +0100
@@ -38,8 +38,7 @@
 from tg.i18n import ugettext as _
 from webob.exc import HTTPBadRequest
 
-import kallithea.lib.helpers as h
-from kallithea.lib import ext_json
+from kallithea.lib import ext_json, webutils
 from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 from kallithea.lib.base import BaseRepoController, jsonify, render
 from kallithea.lib.celerylib.tasks import get_commits_stats
@@ -108,7 +107,7 @@
         try:
             collection = c.db_repo_scm_instance.get_changesets(reverse=True)
         except EmptyRepositoryError as e:
-            h.flash(e, category='warning')
+            webutils.flash(e, category='warning')
             collection = []
         c.cs_pagination = Page(collection, page=p, items_per_page=size)
         page_revisions = [x.raw_id for x in list(c.cs_pagination)]
--- a/kallithea/lib/auth.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/lib/auth.py	Sun Nov 01 04:59:46 2020 +0100
@@ -40,6 +40,7 @@
 from webob.exc import HTTPForbidden, HTTPFound
 
 import kallithea
+from kallithea.lib import webutils
 from kallithea.lib.utils import get_repo_group_slug, get_repo_slug, get_user_group_slug
 from kallithea.lib.utils2 import ascii_bytes, ascii_str, safe_bytes
 from kallithea.lib.vcs.utils.lazy import LazyProperty
@@ -544,9 +545,8 @@
     """Return an exception that must be raised. It will redirect to the login
     page which will redirect back to the current URL after authentication.
     The optional message will be shown in a flash message."""
-    from kallithea.lib import helpers as h
     if message:
-        h.flash(message, category='warning')
+        webutils.flash(message, category='warning')
     p = request.path_qs
     log.debug('Redirecting to login page, origin: %s', p)
     return HTTPFound(location=url('login_home', came_from=p))
--- a/kallithea/lib/base.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/lib/base.py	Sun Nov 01 04:59:46 2020 +0100
@@ -44,7 +44,7 @@
 from tg.i18n import ugettext as _
 
 import kallithea
-from kallithea.lib import auth_modules, ext_json
+from kallithea.lib import auth_modules, ext_json, webutils
 from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware
 from kallithea.lib.exceptions import UserCreationError
 from kallithea.lib.utils import get_repo_slug, is_valid_repo
@@ -361,9 +361,8 @@
             # guaranteed to be side effect free. In practice, the only situation
             # where we allow side effects without ambient authority is when the
             # authority comes from an API key; and that is handled above.
-            from kallithea.lib import helpers as h
-            token = request.POST.get(h.session_csrf_secret_name)
-            if not token or token != h.session_csrf_secret_token():
+            token = request.POST.get(webutils.session_csrf_secret_name)
+            if not token or token != webutils.session_csrf_secret_token():
                 log.error('CSRF check failed')
                 raise webob.exc.HTTPForbidden()
 
@@ -444,8 +443,7 @@
             try:
                 user_info = auth_modules.authenticate('', '', request.environ)
             except UserCreationError as e:
-                from kallithea.lib import helpers as h
-                h.flash(e, 'error', logf=log.error)
+                webutils.flash(e, 'error', logf=log.error)
             else:
                 if user_info is not None:
                     username = user_info['username']
@@ -474,12 +472,11 @@
             raise webob.exc.HTTPMethodNotAllowed()
 
         # Make sure CSRF token never appears in the URL. If so, invalidate it.
-        from kallithea.lib import helpers as h
-        if h.session_csrf_secret_name in request.GET:
+        if webutils.session_csrf_secret_name in request.GET:
             log.error('CSRF key leak detected')
-            session.pop(h.session_csrf_secret_name, None)
+            session.pop(webutils.session_csrf_secret_name, None)
             session.save()
-            h.flash(_('CSRF token leak has been detected - all form tokens have been expired'),
+            webutils.flash(_('CSRF token leak has been detected - all form tokens have been expired'),
                     category='error')
 
         # WebOb already ignores request payload parameters for anything other
@@ -575,8 +572,7 @@
             if c.db_repo_scm_instance is None:
                 log.error('%s this repository is present in database but it '
                           'cannot be created as an scm instance', c.repo_name)
-                from kallithea.lib import helpers as h
-                h.flash(_('Repository not found in the filesystem'),
+                webutils.flash(_('Repository not found in the filesystem'),
                         category='error')
                 raise webob.exc.HTTPNotFound()
 
@@ -592,22 +588,21 @@
         """
         Safe way to get changeset. If error occurs show error.
         """
-        from kallithea.lib import helpers as h
         try:
             return repo.scm_instance.get_ref_revision(ref_type, ref_name)
         except EmptyRepositoryError as e:
             if returnempty:
                 return repo.scm_instance.EMPTY_CHANGESET
-            h.flash(_('There are no changesets yet'), category='error')
+            webutils.flash(_('There are no changesets yet'), category='error')
             raise webob.exc.HTTPNotFound()
         except ChangesetDoesNotExistError as e:
-            h.flash(_('Changeset for %s %s not found in %s') %
+            webutils.flash(_('Changeset for %s %s not found in %s') %
                               (ref_type, ref_name, repo.repo_name),
                     category='error')
             raise webob.exc.HTTPNotFound()
         except RepositoryError as e:
             log.error(traceback.format_exc())
-            h.flash(e, category='error')
+            webutils.flash(e, category='error')
             raise webob.exc.HTTPBadRequest()
 
 
@@ -642,7 +637,6 @@
     If SSH access is disabled in the configuration file, HTTPNotFound is raised.
     """
     if not c.ssh_enabled:
-        from kallithea.lib import helpers as h
-        h.flash(_("SSH access is disabled."), category='warning')
+        webutils.flash(_("SSH access is disabled."), category='warning')
         raise webob.exc.HTTPNotFound()
     return func(*args, **kwargs)
--- a/kallithea/lib/diffs.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/lib/diffs.py	Sun Nov 01 04:59:46 2020 +0100
@@ -31,7 +31,7 @@
 
 from tg.i18n import ugettext as _
 
-from kallithea.lib import helpers as h
+from kallithea.lib import webutils
 from kallithea.lib.utils2 import safe_str
 from kallithea.lib.vcs.backends.base import EmptyChangeset
 from kallithea.lib.vcs.exceptions import VCSError
@@ -207,7 +207,7 @@
     if not html_diff:
         submodules = [o for o in [filenode_new, filenode_old] if isinstance(o, SubModuleNode)]
         if submodules:
-            html_diff = wrap_to_table(h.escape('Submodule %r' % submodules[0]))
+            html_diff = wrap_to_table(webutils.escape('Submodule %r' % submodules[0]))
         else:
             html_diff = wrap_to_table(_('No changes detected'))
 
@@ -249,7 +249,7 @@
         return scm_instance.get_diff(rev1, rev2, path=path,
                                      ignore_whitespace=ignore_whitespace, context=context)
     except MemoryError:
-        h.flash('MemoryError: Diff is too big', category='error')
+        webutils.flash('MemoryError: Diff is too big', category='error')
         return b''
 
 
--- a/kallithea/lib/helpers.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/lib/helpers.py	Sun Nov 01 04:59:46 2020 +0100
@@ -20,7 +20,6 @@
 import hashlib
 import json
 import logging
-import random
 import re
 import textwrap
 import urllib.parse
@@ -28,16 +27,7 @@
 from beaker.cache import cache_region
 from pygments import highlight as code_highlight
 from pygments.formatters.html import HtmlFormatter
-from tg import session
 from tg.i18n import ugettext as _
-from webhelpers2.html import HTML, escape, literal
-from webhelpers2.html.tags import NotGiven, Option, Options, _input, _make_safe_id_component, checkbox, end_form
-from webhelpers2.html.tags import form as insecure_form
-from webhelpers2.html.tags import hidden, link_to, password, radio
-from webhelpers2.html.tags import select as webhelpers2_select
-from webhelpers2.html.tags import submit, text, textarea
-from webhelpers2.number import format_byte_size
-from webhelpers2.text import chop_at, truncate, wrap_paragraphs
 
 import kallithea
 from kallithea.lib.annotate import annotate_highlight
@@ -55,47 +45,49 @@
 # SCM FILTERS available via h.
 #==============================================================================
 from kallithea.lib.vcs.utils import author_email, author_name
-from kallithea.lib.webutils import canonical_url, url
+from kallithea.lib.webutils import (HTML, Option, canonical_url, checkbox, chop_at, end_form, escape, form, format_byte_size, hidden, html_escape, link_to,
+                                    literal, password, pop_flash_messages, radio, reset, safeid, select, session_csrf_secret_name, session_csrf_secret_token,
+                                    submit, text, textarea, truncate, url, wrap_paragraphs)
 from kallithea.model import db
 from kallithea.model.changeset_status import ChangesetStatusModel
 
 
 # mute pyflakes "imported but unused"
+# from webutils
 assert Option
+assert canonical_url
 assert checkbox
+assert chop_at
 assert end_form
+assert form
+assert format_byte_size
+assert hidden
 assert password
+assert pop_flash_messages
 assert radio
+assert reset
+assert safeid
+assert select
+assert session_csrf_secret_name
+assert session_csrf_secret_token
 assert submit
 assert text
 assert textarea
-assert format_byte_size
-assert chop_at
 assert wrap_paragraphs
+# from kallithea.lib.auth
 assert HasPermissionAny
 assert HasRepoGroupPermissionLevel
 assert HasRepoPermissionLevel
+# from utils2
 assert age
 assert time_to_datetime
+# from vcs
 assert EmptyChangeset
-assert canonical_url
 
 
 log = logging.getLogger(__name__)
 
 
-def html_escape(s):
-    """Return string with all html escaped.
-    This is also safe for javascript in html but not necessarily correct.
-    """
-    return (s
-        .replace('&', '&amp;')
-        .replace(">", "&gt;")
-        .replace("<", "&lt;")
-        .replace('"', "&quot;")
-        .replace("'", "&apos;") # Note: this is HTML5 not HTML4 and might not work in mails
-        )
-
 def js(value):
     """Convert Python value to the corresponding JavaScript representation.
 
@@ -152,44 +144,6 @@
     return s
 
 
-def reset(name, value, id=NotGiven, **attrs):
-    """Create a reset button, similar to webhelpers2.html.tags.submit ."""
-    return _input("reset", name, value, id, attrs)
-
-
-def select(name, selected_values, options, id=NotGiven, **attrs):
-    """Convenient wrapper of webhelpers2 to let it accept options as a tuple list"""
-    if isinstance(options, list):
-        option_list = options
-        # Handle old value,label lists ... where value also can be value,label lists
-        options = Options()
-        for x in option_list:
-            if isinstance(x, tuple) and len(x) == 2:
-                value, label = x
-            elif isinstance(x, str):
-                value = label = x
-            else:
-                log.error('invalid select option %r', x)
-                raise
-            if isinstance(value, list):
-                og = options.add_optgroup(label)
-                for x in value:
-                    if isinstance(x, tuple) and len(x) == 2:
-                        group_value, group_label = x
-                    elif isinstance(x, str):
-                        group_value = group_label = x
-                    else:
-                        log.error('invalid select option %r', x)
-                        raise
-                    og.add_option(group_label, group_value)
-            else:
-                options.add_option(label, value)
-    return webhelpers2_select(name, selected_values, options, id=id, **attrs)
-
-
-safeid = _make_safe_id_component
-
-
 def FID(raw_id, path):
     """
     Creates a unique ID for filenode based on it's hash of path and revision
@@ -448,76 +402,6 @@
     return literal(markup_whitespace(annotate_highlight(filenode, url_func, **kwargs)))
 
 
-class _Message(object):
-    """A message returned by ``pop_flash_messages()``.
-
-    Converting the message to a string returns the message text. Instances
-    also have the following attributes:
-
-    * ``category``: the category specified when the message was created.
-    * ``message``: the html-safe message text.
-    """
-
-    def __init__(self, category, message):
-        self.category = category
-        self.message = message
-
-
-def _session_flash_messages(append=None, clear=False):
-    """Manage a message queue in tg.session: return the current message queue
-    after appending the given message, and possibly clearing the queue."""
-    key = 'flash'
-    if key in session:
-        flash_messages = session[key]
-    else:
-        if append is None:  # common fast path - also used for clearing empty queue
-            return []  # don't bother saving
-        flash_messages = []
-        session[key] = flash_messages
-    if append is not None and append not in flash_messages:
-        flash_messages.append(append)
-    if clear:
-        session.pop(key, None)
-    session.save()
-    return flash_messages
-
-
-def flash(message, category, logf=None):
-    """
-    Show a message to the user _and_ log it through the specified function
-
-    category: notice (default), warning, error, success
-    logf: a custom log function - such as log.debug
-
-    logf defaults to log.info, unless category equals 'success', in which
-    case logf defaults to log.debug.
-    """
-    assert category in ('error', 'success', 'warning'), category
-    if hasattr(message, '__html__'):
-        # render to HTML for storing in cookie
-        safe_message = str(message)
-    else:
-        # Apply str - the message might be an exception with __str__
-        # Escape, so we can trust the result without further escaping, without any risk of injection
-        safe_message = html_escape(str(message))
-    if logf is None:
-        logf = log.info
-        if category == 'success':
-            logf = log.debug
-
-    logf('Flash %s: %s', category, safe_message)
-
-    _session_flash_messages(append=(category, safe_message))
-
-
-def pop_flash_messages():
-    """Return all accumulated messages and delete them from the session.
-
-    The return value is a list of ``Message`` objects.
-    """
-    return [_Message(category, message) for category, message in _session_flash_messages(clear=True)]
-
-
 def capitalize(x):
     return x.capitalize()
 
@@ -1317,23 +1201,3 @@
 def ip_range(ip_addr):
     s, e = db.UserIpMap._get_ip_range(ip_addr)
     return '%s - %s' % (s, e)
-
-
-session_csrf_secret_name = "_session_csrf_secret_token"
-
-def session_csrf_secret_token():
-    """Return (and create) the current session's CSRF protection token."""
-    if not session_csrf_secret_name in session:
-        session[session_csrf_secret_name] = str(random.getrandbits(128))
-        session.save()
-    return session[session_csrf_secret_name]
-
-def form(url, method="post", **attrs):
-    """Like webhelpers.html.tags.form , but automatically adding
-    session_csrf_secret_token for POST. The secret is thus never leaked in GET
-    URLs.
-    """
-    form = insecure_form(url, method, **attrs)
-    if method.lower() == 'get':
-        return form
-    return form + HTML.div(hidden(session_csrf_secret_name, session_csrf_secret_token()), style="display: none;")
--- a/kallithea/lib/hooks.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/lib/hooks.py	Sun Nov 01 04:59:46 2020 +0100
@@ -32,7 +32,7 @@
 import mercurial.scmutil
 
 import kallithea
-from kallithea.lib import helpers as h
+from kallithea.lib import webutils
 from kallithea.lib.exceptions import UserCreationError
 from kallithea.lib.utils import action_logger, make_ui
 from kallithea.lib.utils2 import HookEnvironmentError, ascii_str, get_hook_environment, safe_bytes, safe_str
@@ -59,9 +59,9 @@
                 except OSError:
                     pass
 
-    size_scm_f = h.format_byte_size(size_scm)
-    size_root_f = h.format_byte_size(size_root)
-    size_total_f = h.format_byte_size(size_root + size_scm)
+    size_scm_f = webutils.format_byte_size(size_scm)
+    size_root_f = webutils.format_byte_size(size_root)
+    size_total_f = webutils.format_byte_size(size_root + size_scm)
 
     return size_scm_f, size_root_f, size_total_f
 
--- a/kallithea/lib/page.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/lib/page.py	Sun Nov 01 04:59:46 2020 +0100
@@ -19,9 +19,8 @@
 import paginate
 import paginate_sqlalchemy
 import sqlalchemy.orm
-from webhelpers2.html import literal
 
-from kallithea.lib.webutils import url
+from kallithea.lib import webutils
 
 
 log = logging.getLogger(__name__)
@@ -35,10 +34,10 @@
         if isinstance(collection, sqlalchemy.orm.query.Query):
             collection = paginate_sqlalchemy.SqlalchemyOrmWrapper(collection)
         paginate.Page.__init__(self, collection, page=page, items_per_page=items_per_page, item_count=item_count,
-                               url_maker=lambda page: url.current(page=page, **kwargs))
+                               url_maker=lambda page: webutils.url.current(page=page, **kwargs))
 
     def pager(self):
-        return literal(
+        return webutils.literal(
             paginate.Page.pager(self,
                 format='<ul class="pagination">$link_previous\n~4~$link_next</ul>',
                 link_attr={'class': 'pager_link'},
--- a/kallithea/lib/webutils.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/lib/webutils.py	Sun Nov 01 04:59:46 2020 +0100
@@ -20,11 +20,46 @@
 imported anywhere - just like the global variables can be used everywhere.
 """
 
-from tg import request
+import logging
+import random
+
+from tg import request, session
+from webhelpers2.html import HTML, escape, literal
+from webhelpers2.html.tags import NotGiven, Option, Options, _input
+from webhelpers2.html.tags import _make_safe_id_component as safeid
+from webhelpers2.html.tags import checkbox, end_form
+from webhelpers2.html.tags import form as insecure_form
+from webhelpers2.html.tags import hidden, link_to, password, radio
+from webhelpers2.html.tags import select as webhelpers2_select
+from webhelpers2.html.tags import submit, text, textarea
+from webhelpers2.number import format_byte_size
+from webhelpers2.text import chop_at, truncate, wrap_paragraphs
 
 import kallithea
 
 
+log = logging.getLogger(__name__)
+
+
+# mute pyflakes "imported but unused"
+assert Option
+assert checkbox
+assert chop_at
+assert end_form
+assert escape
+assert format_byte_size
+assert link_to
+assert literal
+assert password
+assert radio
+assert safeid
+assert submit
+assert text
+assert textarea
+assert truncate
+assert wrap_paragraphs
+
+
 #
 # General Kallithea URL handling
 #
@@ -75,3 +110,149 @@
     except IndexError:
         parts = url('home', qualified=True).split('://', 1)
         return parts[1].split('/', 1)[0]
+
+
+#
+# Custom Webhelpers2 stuff
+#
+
+def html_escape(s):
+    """Return string with all html escaped.
+    This is also safe for javascript in html but not necessarily correct.
+    """
+    return (s
+        .replace('&', '&amp;')
+        .replace(">", "&gt;")
+        .replace("<", "&lt;")
+        .replace('"', "&quot;")
+        .replace("'", "&apos;") # Note: this is HTML5 not HTML4 and might not work in mails
+        )
+
+
+def reset(name, value, id=NotGiven, **attrs):
+    """Create a reset button, similar to webhelpers2.html.tags.submit ."""
+    return _input("reset", name, value, id, attrs)
+
+
+def select(name, selected_values, options, id=NotGiven, **attrs):
+    """Convenient wrapper of webhelpers2 to let it accept options as a tuple list"""
+    if isinstance(options, list):
+        option_list = options
+        # Handle old value,label lists ... where value also can be value,label lists
+        options = Options()
+        for x in option_list:
+            if isinstance(x, tuple) and len(x) == 2:
+                value, label = x
+            elif isinstance(x, str):
+                value = label = x
+            else:
+                log.error('invalid select option %r', x)
+                raise
+            if isinstance(value, list):
+                og = options.add_optgroup(label)
+                for x in value:
+                    if isinstance(x, tuple) and len(x) == 2:
+                        group_value, group_label = x
+                    elif isinstance(x, str):
+                        group_value = group_label = x
+                    else:
+                        log.error('invalid select option %r', x)
+                        raise
+                    og.add_option(group_label, group_value)
+            else:
+                options.add_option(label, value)
+    return webhelpers2_select(name, selected_values, options, id=id, **attrs)
+
+
+session_csrf_secret_name = "_session_csrf_secret_token"
+
+def session_csrf_secret_token():
+    """Return (and create) the current session's CSRF protection token."""
+    if not session_csrf_secret_name in session:
+        session[session_csrf_secret_name] = str(random.getrandbits(128))
+        session.save()
+    return session[session_csrf_secret_name]
+
+def form(url, method="post", **attrs):
+    """Like webhelpers.html.tags.form , but automatically adding
+    session_csrf_secret_token for POST. The secret is thus never leaked in GET
+    URLs.
+    """
+    form = insecure_form(url, method, **attrs)
+    if method.lower() == 'get':
+        return form
+    return form + HTML.div(hidden(session_csrf_secret_name, session_csrf_secret_token()), style="display: none;")
+
+
+#
+# Flash messages, stored in cookie
+#
+
+class _Message(object):
+    """A message returned by ``pop_flash_messages()``.
+
+    Converting the message to a string returns the message text. Instances
+    also have the following attributes:
+
+    * ``category``: the category specified when the message was created.
+    * ``message``: the html-safe message text.
+    """
+
+    def __init__(self, category, message):
+        self.category = category
+        self.message = message
+
+
+def _session_flash_messages(append=None, clear=False):
+    """Manage a message queue in tg.session: return the current message queue
+    after appending the given message, and possibly clearing the queue."""
+    key = 'flash'
+    if key in session:
+        flash_messages = session[key]
+    else:
+        if append is None:  # common fast path - also used for clearing empty queue
+            return []  # don't bother saving
+        flash_messages = []
+        session[key] = flash_messages
+    if append is not None and append not in flash_messages:
+        flash_messages.append(append)
+    if clear:
+        session.pop(key, None)
+    session.save()
+    return flash_messages
+
+
+def flash(message, category, logf=None):
+    """
+    Show a message to the user _and_ log it through the specified function
+
+    category: notice (default), warning, error, success
+    logf: a custom log function - such as log.debug
+
+    logf defaults to log.info, unless category equals 'success', in which
+    case logf defaults to log.debug.
+    """
+    assert category in ('error', 'success', 'warning'), category
+    if hasattr(message, '__html__'):
+        # render to HTML for storing in cookie
+        safe_message = str(message)
+    else:
+        # Apply str - the message might be an exception with __str__
+        # Escape, so we can trust the result without further escaping, without any risk of injection
+        safe_message = html_escape(str(message))
+    if logf is None:
+        logf = log.info
+        if category == 'success':
+            logf = log.debug
+
+    logf('Flash %s: %s', category, safe_message)
+
+    _session_flash_messages(append=(category, safe_message))
+
+
+def pop_flash_messages():
+    """Return all accumulated messages and delete them from the session.
+
+    The return value is a list of ``Message`` objects.
+    """
+    return [_Message(category, message) for category, message in _session_flash_messages(clear=True)]
--- a/kallithea/model/comment.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/model/comment.py	Sun Nov 01 04:59:46 2020 +0100
@@ -81,7 +81,7 @@
                 repo_name=repo.repo_name,
                 revision=revision,
                 anchor='comment-%s' % comment.comment_id)
-            subj = h.link_to(
+            subj = webutils.link_to(
                 'Re changeset: %(desc)s %(line)s' %
                           {'desc': desc, 'line': line},
                  comment_url)
@@ -127,7 +127,7 @@
                                                           webutils.canonical_hostname()))
             comment_url = pull_request.url(canonical=True,
                 anchor='comment-%s' % comment.comment_id)
-            subj = h.link_to(
+            subj = webutils.link_to(
                 'Re pull request %(pr_nice_id)s: %(desc)s %(line)s' %
                           {'desc': desc,
                            'pr_nice_id': comment.pull_request.nice_id(),
--- a/kallithea/model/db.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/model/db.py	Sun Nov 01 04:59:46 2020 +0100
@@ -1306,9 +1306,8 @@
         return grouped
 
     def _repo_size(self):
-        from kallithea.lib import helpers as h
         log.debug('calculating repository size...')
-        return h.format_byte_size(self.scm_instance.size)
+        return webutils.format_byte_size(self.scm_instance.size)
 
     #==========================================================================
     # SCM CACHE INSTANCE
@@ -1397,10 +1396,9 @@
     @classmethod
     def _generate_choice(cls, repo_group):
         """Return tuple with group_id and name as html literal"""
-        from webhelpers2.html import literal
         if repo_group is None:
             return (-1, '-- %s --' % _('top level'))
-        return repo_group.group_id, literal(cls.SEP.join(repo_group.full_path_splitted))
+        return repo_group.group_id, webutils.literal(cls.SEP.join(repo_group.full_path_splitted))
 
     @classmethod
     def groups_choices(cls, groups):
--- a/kallithea/model/pull_request.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/model/pull_request.py	Sun Nov 01 04:59:46 2020 +0100
@@ -82,7 +82,7 @@
         threading = ['%s-pr-%s@%s' % (pr.other_repo.repo_name,
                                       pr.pull_request_id,
                                       webutils.canonical_hostname())]
-        subject = h.link_to(
+        subject = webutils.link_to(
             _('%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s') %
                 {'user': user.username,
                  'pr_title': pr.title,
--- a/kallithea/model/user.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/model/user.py	Sun Nov 01 04:59:46 2020 +0100
@@ -314,7 +314,6 @@
         allowing users to copy-paste or manually enter the token from the
         email.
         """
-        import kallithea.lib.helpers as h
         from kallithea.lib.celerylib import tasks
         from kallithea.model.notification import EmailNotificationModel
 
@@ -326,7 +325,7 @@
                 log.debug('password reset user %s found', user)
                 token = self.get_reset_password_token(user,
                                                       timestamp,
-                                                      h.session_csrf_secret_token())
+                                                      webutils.session_csrf_secret_token())
                 # URL must be fully qualified; but since the token is locked to
                 # the current browser session, we must provide a URL with the
                 # current scheme and hostname, rather than the canonical_url.
@@ -359,7 +358,6 @@
                      timestamp=timestamp)
 
     def verify_reset_password_token(self, email, timestamp, token):
-        import kallithea.lib.helpers as h
         user = db.User.get_by_email(email)
         if user is None:
             log.debug("user with email %s not found", email)
@@ -377,7 +375,7 @@
 
         expected_token = self.get_reset_password_token(user,
                                                        timestamp,
-                                                       h.session_csrf_secret_token())
+                                                       webutils.session_csrf_secret_token())
         log.debug('computed password reset token: %s', expected_token)
         log.debug('received password reset token: %s', token)
         return expected_token == token
--- a/kallithea/tests/functional/test_admin_users.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/tests/functional/test_admin_users.py	Sun Nov 01 04:59:46 2020 +0100
@@ -19,7 +19,7 @@
 
 import kallithea
 from kallithea.controllers.admin.users import UsersController
-from kallithea.lib import helpers as h
+from kallithea.lib import webutils
 from kallithea.lib.auth import check_password
 from kallithea.model import db, meta, validators
 from kallithea.model.user import UserModel
@@ -112,7 +112,7 @@
 
         with test_context(self.app):
             msg = validators.ValidUsername(False, {})._messages['system_invalid_username']
-        msg = h.html_escape(msg % {'username': 'new_user'})
+        msg = webutils.html_escape(msg % {'username': 'new_user'})
         response.mustcontain("""<span class="error-message">%s</span>""" % msg)
         response.mustcontain("""<span class="error-message">Please enter a value</span>""")
         response.mustcontain("""<span class="error-message">An email address must contain a single @</span>""")
@@ -569,7 +569,7 @@
         # flash complains about an non-existing session
         def flash_mock(*args, **kwargs):
             pass
-        monkeypatch.setattr(h, 'flash', flash_mock)
+        monkeypatch.setattr(webutils, 'flash', flash_mock)
 
         u = UsersController()
         # a regular user should work correctly
--- a/kallithea/tests/functional/test_login.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/tests/functional/test_login.py	Sun Nov 01 04:59:46 2020 +0100
@@ -7,7 +7,7 @@
 from tg.util.webtest import test_context
 
 import kallithea.lib.celerylib.tasks
-from kallithea.lib import helpers as h
+from kallithea.lib import webutils
 from kallithea.lib.auth import check_password
 from kallithea.lib.utils2 import generate_api_key
 from kallithea.model import db, meta, validators
@@ -238,7 +238,7 @@
 
         with test_context(self.app):
             msg = validators.ValidUsername()._messages['username_exists']
-        msg = h.html_escape(msg % {'username': uname})
+        msg = webutils.html_escape(msg % {'username': uname})
         response.mustcontain(msg)
 
     def test_register_err_same_email(self):
@@ -311,7 +311,7 @@
         response.mustcontain('An email address must contain a single @')
         with test_context(self.app):
             msg = validators.ValidUsername()._messages['username_exists']
-        msg = h.html_escape(msg % {'username': usr})
+        msg = webutils.html_escape(msg % {'username': usr})
         response.mustcontain(msg)
 
     def test_register_special_chars(self):
--- a/kallithea/tests/functional/test_my_account.py	Thu Oct 29 14:32:42 2020 +0100
+++ b/kallithea/tests/functional/test_my_account.py	Sun Nov 01 04:59:46 2020 +0100
@@ -2,7 +2,7 @@
 
 from tg.util.webtest import test_context
 
-from kallithea.lib import helpers as h
+from kallithea.lib import webutils
 from kallithea.model import db, meta
 from kallithea.model.user import UserModel
 from kallithea.tests import base
@@ -186,7 +186,7 @@
         with test_context(self.app):
             msg = validators.ValidUsername(edit=False, old_data={}) \
                     ._messages['username_exists']
-        msg = h.html_escape(msg % {'username': base.TEST_USER_ADMIN_LOGIN})
+        msg = webutils.html_escape(msg % {'username': base.TEST_USER_ADMIN_LOGIN})
         response.mustcontain(msg)
 
     def test_my_account_api_keys(self):