From 8efed2327dce3cbba7d4eac1fe5e5844993b4623 Mon Sep 17 00:00:00 2001
From: Jan Mach <jan.mach@cesnet.cz>
Date: Mon, 21 Mar 2022 17:36:16 +0100
Subject: [PATCH] Merged all of Vial into Hawat

Unfinished, unpolished work.

(Redmine issue: #7544)
---
 lib/hawat/app.py                              |   26 +-
 lib/hawat/base.py                             |  994 ++++++++++++-
 lib/hawat/blueprints/auth/__init__.py         |    6 +-
 .../blueprints/auth_dev/test/__init__.py      |    6 +-
 lib/hawat/blueprints/auth_env/__init__.py     |   89 +-
 lib/hawat/blueprints/auth_env/forms.py        |    2 +-
 .../blueprints/auth_env/test/__init__.py      |    6 +-
 lib/hawat/blueprints/auth_pwd/__init__.py     |   93 +-
 lib/hawat/blueprints/auth_pwd/forms.py        |   27 +-
 .../blueprints/auth_pwd/test/__init__.py      |    6 +-
 .../blueprints/changelogs/__init__.py         |   22 +-
 .../blueprints/changelogs/forms.py            |   24 +-
 .../templates/changelogs/search.html          |    0
 .../changelogs/templates/changelogs/show.html |    0
 .../blueprints/changelogs/test/__init__.py    |   20 +-
 lib/hawat/blueprints/dbstatus/__init__.py     |   42 +-
 .../blueprints/dbstatus/test/__init__.py      |   36 +-
 .../blueprints/design_bs3/__init__.py         |    8 +-
 .../design_bs3/templates/_layout.html         |    0
 .../templates/_layout_confirmation.html       |    0
 .../templates/_layout_creatupdate.html        |    0
 .../templates/_layout_events_search.html      |    0
 .../design_bs3/templates/_layout_list.html    |    0
 .../design_bs3/templates/_layout_login.html   |    0
 .../templates/_layout_registration.html       |    0
 .../design_bs3/templates/_layout_search.html  |    0
 .../design_bs3/templates/_macros_chart.html   |    0
 .../design_bs3/templates/_macros_form.html    |    0
 .../design_bs3/templates/_macros_page.html    |    0
 .../design_bs3/templates/_macros_site.html    |    0
 .../design_bs3/templates/form_delete.html     |    0
 .../design_bs3/templates/form_disable.html    |    0
 .../design_bs3/templates/form_enable.html     |    0
 .../design_bs3/templates/http_error.html      |    0
 .../templates/spt_flashmessage.html           |    0
 .../blueprints/devtools/__init__.py           |   16 +-
 .../devtools/templates/devtools/config.html   |    0
 .../blueprints/devtools/test/__init__.py      |   20 +-
 lib/hawat/blueprints/dnsr/__init__.py         |   18 +-
 lib/hawat/blueprints/dnsr/test/__init__.py    |   16 +-
 lib/hawat/blueprints/events/__init__.py       |   26 +-
 lib/hawat/blueprints/events/forms.py          |   52 +-
 lib/hawat/blueprints/events/test/__init__.py  |   26 +-
 lib/hawat/blueprints/filters/__init__.py      |   52 +-
 lib/hawat/blueprints/filters/forms.py         |   26 +-
 lib/hawat/blueprints/filters/test/__init__.py |  170 +--
 lib/hawat/blueprints/geoip/__init__.py        |   18 +-
 lib/hawat/blueprints/geoip/forms.py           |    4 +-
 lib/hawat/blueprints/geoip/test/__init__.py   |   16 +-
 lib/hawat/blueprints/groups/__init__.py       |  912 +++++++++++-
 lib/hawat/blueprints/groups/forms.py          |  133 +-
 lib/hawat/blueprints/groups/test/__init__.py  |  150 +-
 lib/hawat/blueprints/home/__init__.py         |   12 +-
 lib/hawat/blueprints/home/test/__init__.py    |   16 +-
 lib/hawat/blueprints/hosts/__init__.py        |   20 +-
 lib/hawat/blueprints/hosts/forms.py           |   12 +-
 lib/hawat/blueprints/hosts/test/__init__.py   |   16 +-
 lib/hawat/blueprints/nerd/__init__.py         |   20 +-
 lib/hawat/blueprints/nerd/forms.py            |    4 +-
 lib/hawat/blueprints/nerd/test/__init__.py    |   16 +-
 lib/hawat/blueprints/networks/__init__.py     |   42 +-
 lib/hawat/blueprints/networks/forms.py        |   18 +-
 .../blueprints/networks/test/__init__.py      |  144 +-
 lib/hawat/blueprints/pdnsr/__init__.py        |   18 +-
 lib/hawat/blueprints/pdnsr/forms.py           |    4 +-
 lib/hawat/blueprints/pdnsr/test/__init__.py   |   16 +-
 lib/hawat/blueprints/performance/__init__.py  |   12 +-
 .../blueprints/performance/test/__init__.py   |   16 +-
 lib/hawat/blueprints/reports/__init__.py      |   30 +-
 lib/hawat/blueprints/reports/forms.py         |   28 +-
 lib/hawat/blueprints/reports/test/__init__.py |   16 +-
 .../blueprints/settings_reporting/__init__.py |   32 +-
 .../blueprints/settings_reporting/forms.py    |   30 +-
 lib/hawat/blueprints/skeleton/__init__.py     |   12 +-
 lib/hawat/blueprints/status/__init__.py       |   16 +-
 lib/hawat/blueprints/status/test/__init__.py  |   16 +-
 lib/hawat/blueprints/timeline/__init__.py     |   22 +-
 lib/hawat/blueprints/timeline/forms.py        |   42 +-
 .../blueprints/timeline/test/__init__.py      |   16 +-
 lib/hawat/blueprints/users/__init__.py        | 1107 ++++++++++++++-
 lib/hawat/blueprints/users/forms.py           |  141 +-
 lib/hawat/blueprints/users/test/__init__.py   |  136 +-
 lib/hawat/blueprints/whois/__init__.py        |   18 +-
 lib/hawat/blueprints/whois/test/__init__.py   |   16 +-
 lib/hawat/test/__init__.py                    |  580 +++++++-
 lib/{vial => hawat}/test/fixtures.py          |    0
 lib/hawat/test/runner.py                      |    4 +-
 lib/vial/__init__.py                          |   63 -
 lib/vial/app.py                               | 1038 --------------
 lib/vial/blueprints/__init__.py               |    0
 lib/vial/blueprints/auth_env/__init__.py      |  290 ----
 lib/vial/blueprints/auth_env/forms.py         |   37 -
 .../templates/auth_env/registration.html      |   26 -
 .../auth_env/registration/email_admins.txt    |   26 -
 .../auth_env/registration/email_managers.txt  |   19 -
 .../auth_env/registration/email_user.txt      |   30 -
 lib/vial/blueprints/auth_env/test/__init__.py |  188 ---
 lib/vial/blueprints/auth_pwd/__init__.py      |  148 --
 lib/vial/blueprints/auth_pwd/forms.py         |   89 --
 .../auth_pwd/templates/auth_pwd/login.html    |    6 -
 .../templates/auth_pwd/registration.html      |   24 -
 .../templates/registration/email_admins.txt   |   26 -
 .../templates/registration/email_managers.txt |   19 -
 .../templates/registration/email_user.txt     |   30 -
 lib/vial/blueprints/auth_pwd/test/__init__.py |  176 ---
 lib/vial/blueprints/groups/__init__.py        | 1108 ---------------
 lib/vial/blueprints/groups/forms.py           |  255 ----
 .../groups/templates/groups/addmember.html    |   16 -
 .../groups/templates/groups/creatupdate.html  |   32 -
 .../groups/templates/groups/list.html         |   78 --
 .../groups/templates/groups/rejectmember.html |   16 -
 .../groups/templates/groups/removemember.html |   16 -
 .../groups/templates/groups/show.html         |  187 ---
 lib/vial/blueprints/groups/test/__init__.py   |  541 --------
 lib/vial/blueprints/home/__init__.py          |   85 --
 .../blueprints/home/templates/home/index.html |   39 -
 lib/vial/blueprints/home/test/__init__.py     |   70 -
 lib/vial/blueprints/users/__init__.py         | 1230 -----------------
 lib/vial/blueprints/users/forms.py            |  284 ----
 .../users/templates/users/addmembership.html  |   16 -
 .../users/templates/users/creatupdate.html    |   38 -
 .../templates/users/email_activation.txt      |    9 -
 .../users/templates/users/list.html           |   78 --
 .../templates/users/rejectmembership.html     |   16 -
 .../templates/users/removemembership.html     |   16 -
 .../users/templates/users/show.html           |  281 ----
 lib/vial/blueprints/users/test/__init__.py    |  606 --------
 lib/vial/blueprints/users/test/utils.py       |  209 ---
 lib/vial/test/__init__.py                     |  600 --------
 lib/vial/test/runner.py                       |   60 -
 130 files changed, 4811 insertions(+), 9020 deletions(-)
 rename lib/{vial => hawat}/blueprints/changelogs/__init__.py (91%)
 rename lib/{vial => hawat}/blueprints/changelogs/forms.py (84%)
 rename lib/{vial => hawat}/blueprints/changelogs/templates/changelogs/search.html (100%)
 rename lib/{vial => hawat}/blueprints/changelogs/templates/changelogs/show.html (100%)
 rename lib/{vial => hawat}/blueprints/changelogs/test/__init__.py (73%)
 rename lib/{vial => hawat}/blueprints/design_bs3/__init__.py (88%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/_layout.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/_layout_confirmation.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/_layout_creatupdate.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/_layout_events_search.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/_layout_list.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/_layout_login.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/_layout_registration.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/_layout_search.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/_macros_chart.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/_macros_form.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/_macros_page.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/_macros_site.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/form_delete.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/form_disable.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/form_enable.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/http_error.html (100%)
 rename lib/{vial => hawat}/blueprints/design_bs3/templates/spt_flashmessage.html (100%)
 rename lib/{vial => hawat}/blueprints/devtools/__init__.py (84%)
 rename lib/{vial => hawat}/blueprints/devtools/templates/devtools/config.html (100%)
 rename lib/{vial => hawat}/blueprints/devtools/test/__init__.py (78%)
 rename lib/{vial => hawat}/test/fixtures.py (100%)
 delete mode 100644 lib/vial/__init__.py
 delete mode 100644 lib/vial/app.py
 delete mode 100644 lib/vial/blueprints/__init__.py
 delete mode 100644 lib/vial/blueprints/auth_env/__init__.py
 delete mode 100644 lib/vial/blueprints/auth_env/forms.py
 delete mode 100644 lib/vial/blueprints/auth_env/templates/auth_env/registration.html
 delete mode 100644 lib/vial/blueprints/auth_env/templates/auth_env/registration/email_admins.txt
 delete mode 100644 lib/vial/blueprints/auth_env/templates/auth_env/registration/email_managers.txt
 delete mode 100644 lib/vial/blueprints/auth_env/templates/auth_env/registration/email_user.txt
 delete mode 100644 lib/vial/blueprints/auth_env/test/__init__.py
 delete mode 100644 lib/vial/blueprints/auth_pwd/__init__.py
 delete mode 100644 lib/vial/blueprints/auth_pwd/forms.py
 delete mode 100644 lib/vial/blueprints/auth_pwd/templates/auth_pwd/login.html
 delete mode 100644 lib/vial/blueprints/auth_pwd/templates/auth_pwd/registration.html
 delete mode 100644 lib/vial/blueprints/auth_pwd/templates/registration/email_admins.txt
 delete mode 100644 lib/vial/blueprints/auth_pwd/templates/registration/email_managers.txt
 delete mode 100644 lib/vial/blueprints/auth_pwd/templates/registration/email_user.txt
 delete mode 100644 lib/vial/blueprints/auth_pwd/test/__init__.py
 delete mode 100644 lib/vial/blueprints/groups/__init__.py
 delete mode 100644 lib/vial/blueprints/groups/forms.py
 delete mode 100644 lib/vial/blueprints/groups/templates/groups/addmember.html
 delete mode 100644 lib/vial/blueprints/groups/templates/groups/creatupdate.html
 delete mode 100644 lib/vial/blueprints/groups/templates/groups/list.html
 delete mode 100644 lib/vial/blueprints/groups/templates/groups/rejectmember.html
 delete mode 100644 lib/vial/blueprints/groups/templates/groups/removemember.html
 delete mode 100644 lib/vial/blueprints/groups/templates/groups/show.html
 delete mode 100644 lib/vial/blueprints/groups/test/__init__.py
 delete mode 100644 lib/vial/blueprints/home/__init__.py
 delete mode 100644 lib/vial/blueprints/home/templates/home/index.html
 delete mode 100644 lib/vial/blueprints/home/test/__init__.py
 delete mode 100644 lib/vial/blueprints/users/__init__.py
 delete mode 100644 lib/vial/blueprints/users/forms.py
 delete mode 100644 lib/vial/blueprints/users/templates/users/addmembership.html
 delete mode 100644 lib/vial/blueprints/users/templates/users/creatupdate.html
 delete mode 100644 lib/vial/blueprints/users/templates/users/email_activation.txt
 delete mode 100644 lib/vial/blueprints/users/templates/users/list.html
 delete mode 100644 lib/vial/blueprints/users/templates/users/rejectmembership.html
 delete mode 100644 lib/vial/blueprints/users/templates/users/removemembership.html
 delete mode 100644 lib/vial/blueprints/users/templates/users/show.html
 delete mode 100644 lib/vial/blueprints/users/test/__init__.py
 delete mode 100644 lib/vial/blueprints/users/test/utils.py
 delete mode 100644 lib/vial/test/__init__.py
 delete mode 100644 lib/vial/test/runner.py

diff --git a/lib/hawat/app.py b/lib/hawat/app.py
index 8f860f579..1f31db67f 100644
--- a/lib/hawat/app.py
+++ b/lib/hawat/app.py
@@ -65,16 +65,22 @@ def create_app_full(
     :return: Hawat application
     :rtype: hawat.base.HawatApp
     """
-
-    return vial.create_app_full(
-        hawat.base.HawatApp,
-        APP_NAME,
-        config_dict   = config_dict,
-        config_object = config_object,
-        config_file   = config_file,
-        config_env    = config_env,
-        config_func   = config_func or _config_app
-    )
+    app = hawat.base.HawatApp(APP_NAME)
+
+    if config_dict and isinstance(config_dict, dict):
+        app.config.update(config_dict)
+    if config_object:
+        app.config.from_object(config_object)
+    if config_file:
+        app.config.from_pyfile(config_file)
+    if config_env and os.getenv(config_env, None):
+        app.config.from_envvar(config_env)
+    if config_func and callable(config_func):
+        config_func(app.config)
+
+    app.setup_app()
+
+    return app
 
 
 def create_app():
diff --git a/lib/hawat/base.py b/lib/hawat/base.py
index 04134ed3a..b0fd3ce94 100644
--- a/lib/hawat/base.py
+++ b/lib/hawat/base.py
@@ -13,7 +13,34 @@ __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea
 
 
 import re
+import sys
+import traceback
 import datetime
+import weakref
+import json
+import jinja2
+
+import werkzeug.routing
+import werkzeug.utils
+import flask
+import flask.app
+import flask.views
+import flask_babel
+import flask_migrate
+import flask_login
+import flask_principal
+
+import hawat.const
+import vial.acl
+import vial.log
+import vial.mailer
+import vial.intl
+import vial.errors
+import vial.utils
+import vial.jsglue
+import vial.view
+import vial.menu
+import vial.command
 
 #
 # Flask related modules.
@@ -47,7 +74,12 @@ CRE_QNAME = re.compile(r'^([\d]+)_([a-z]{6})$')
 RE_UQUERY = ' AS "_mentatq\\({:d}_[^)]+\\)_"'
 
 
-class HawatApp(vial.app.Vial):
+class HawatException(Exception):
+    """
+    Custom class for :py:class:`vial.app.Vial` application exceptions.
+    """
+
+class HawatApp(flask.Flask):
     """
     Custom implementation of :py:class:`flask.Flask` class. This class extends the
     capabilities of the base class with following additional features:
@@ -70,6 +102,19 @@ class HawatApp(vial.app.Vial):
     def __init__(self, import_name, **kwargs):
         super().__init__(import_name, **kwargs)
 
+        self.csrf = None
+
+        self.mailer = None
+
+        self.menu_main = vial.menu.Menu()
+        self.menu_auth = vial.menu.Menu()
+        self.menu_anon = vial.menu.Menu()
+
+        self.sign_ins    = {}
+        self.sign_ups    = {}
+        self.resources   = {}
+        self.infomailers = {}
+
         self.csag = {}
         self.oads = {}
 
@@ -80,6 +125,235 @@ class HawatApp(vial.app.Vial):
         """
         return self.config[hawat.const.CFGKEY_MENTAT_CORE]
 
+    @property
+    def icons(self):
+        """
+        Application icon registry.
+        """
+        return self.config.get('ICONS')
+
+    @flask.app.setupmethod
+    def add_url_rule(self, rule, endpoint = None, view_func = None, provide_automatic_options = None, **options):
+        """
+        Reimplementation of :py:func:`flask.Flask.add_url_rule` method. This method
+        is capable of disabling selected application endpoints. Keep in mind, that
+        some URL rules (like application global 'static' endpoint) are created during
+        the :py:func:`flask.app.Flask.__init__` method and cannot be disabled,
+        because at that point the configuration of the application is not yet loaded.
+        """
+        if self.config.get('DISABLED_ENDPOINTS', None) and self.config['DISABLED_ENDPOINTS'] and endpoint:
+            if endpoint in self.config['DISABLED_ENDPOINTS']:
+                self.logger.warning(  # pylint: disable=locally-disabled,no-member
+                    "Application endpoint '%s' is disabled by configuration.",
+                    endpoint
+                )
+                return
+        #self.logger.debug(  # pylint: disable=locally-disabled,no-member
+        #    "Registering URL route %s:%s:%s:%s",
+        #    str(rule),
+        #    str(endpoint),
+        #    str(view_func),
+        #    str(view_func.view_class) if hasattr(view_func, 'view_class') else '---none---',
+        #)
+        super().add_url_rule(rule, endpoint, view_func, provide_automatic_options, **options)
+
+    def register_blueprint(self, blueprint, **options):
+        """
+        Reimplementation of :py:func:`flask.Flask.register_blueprint` method. This
+        method will perform standart blueprint registration and on top of that will
+        perform following additional tasks:
+
+            * Register blueprint into custom internal registry. The registry lies
+              within application`s ``config`` under key :py:const:`hawat.const.CFGKEY_ENABLED_BLUEPRINTS`.
+            * Call blueprint`s ``register_app`` method, if available, with ``self`` as only argument.
+
+        :param vial.app.VialBlueprint blueprint: Blueprint to be registered.
+        :param dict options: Additional options, will be passed down to :py:func:`flask.Flask.register_blueprint`.
+        """
+        super().register_blueprint(blueprint, **options)
+
+        if isinstance(blueprint, VialBlueprint):
+            if hasattr(blueprint, 'register_app'):
+                blueprint.register_app(self)
+
+            self.sign_ins.update(blueprint.sign_ins)
+            self.sign_ups.update(blueprint.sign_ups)
+
+    def register_blueprints(self):
+        """
+        Register all configured application blueprints. The configuration comes
+        from :py:const:`hawat.const.CFGKEY_ENABLED_BLUEPRINTS` configuration
+        subkey, which must contain list of string names of required blueprints.
+        The blueprint module must provide ``get_blueprint`` factory method, that
+        must return valid instance of :py:class:`vial.app.VialBlueprint`. This
+        method will call the :py:func:`vial.app.Vial.register_blueprint` for
+        each blueprint, that is being registered into the application.
+
+        :raises vial.app.VialException: In case the factory method ``get_blueprint`` is not provided by loaded module.
+        """
+        for name in self.config[hawat.const.CFGKEY_ENABLED_BLUEPRINTS]:
+            self.logger.debug(  # pylint: disable=locally-disabled,no-member
+                "Loading pluggable module %s",
+                name
+            )
+            mod = werkzeug.utils.import_string(name)
+            if hasattr(mod, 'get_blueprint'):
+                self.register_blueprint(mod.get_blueprint())
+            else:
+                raise VialException(
+                    "Invalid blueprint module '{}', does not provide the 'get_blueprint' factory method.".format(name)
+                )
+
+    def log_exception(self, exc_info):
+        """
+        Reimplementation of :py:func:`flask.Flask.log_exception` method.
+        """
+        self.logger.error(  # pylint: disable=locally-disabled,no-member
+            "Exception on %s [%s]" % (flask.request.full_path, flask.request.method),
+            exc_info = exc_info
+        )
+
+    def log_exception_with_label(self, tbexc, label = ''):
+        """
+        Log given exception traceback into application logger.
+        """
+        self.logger.error(  # pylint: disable=locally-disabled,no-member
+            '%s%s',
+            label,
+            ''.join(tbexc.format())
+        )
+
+    #--------------------------------------------------------------------------
+
+    def get_modules(self, filter_func = None):
+        """
+        Get all currently registered application modules.
+        """
+        if not filter_func:
+            return self.blueprints
+        return {
+            k: v for k, v in self.blueprints.items() if filter_func(k, v)
+        }
+
+    def has_endpoint(self, endpoint):
+        """
+        Check if given routing endpoint is available.
+
+        :param str endpoint: Application routing endpoint.
+        :return: ``True`` in case endpoint exists, ``False`` otherwise.
+        :rtype: bool
+        """
+        return endpoint in self.view_functions
+
+    def get_endpoints(self, filter_func = None):
+        """
+        Get all currently registered application endpoints.
+        """
+        if not filter_func:
+            return {
+                k: v.view_class for k, v in self.view_functions.items() if hasattr(v, 'view_class')
+            }
+        return {
+            k: v.view_class for k, v in self.view_functions.items() if hasattr(v, 'view_class') and filter_func(k, v.view_class)
+        }
+
+    def get_endpoint_class(self, endpoint, quiet = False):
+        """
+        Get reference to view class registered to given routing endpoint.
+
+        :param str endpoint: Application routing endpoint.
+        :param bool quiet: Suppress the exception in case given endpoint does not exist.
+        :return: Reference to view class.
+        :rtype: class
+        """
+        if not endpoint in self.view_functions:
+            if quiet:
+                return None
+            raise VialException(
+                "Unknown endpoint name '{}'.".format(endpoint)
+            )
+        try:
+            return self.view_functions[endpoint].view_class
+        except AttributeError:
+            return vial.view.DecoratedView(self.view_functions[endpoint])
+
+    def can_access_endpoint(self, endpoint, **kwargs):
+        """
+        Check, that the current user can access given endpoint/view.
+
+        :param str endpoint: Application routing endpoint.
+        :param dict kwargs: Optional endpoint parameters.
+        :return: ``True`` in case user can access the endpoint, ``False`` otherwise.
+        :rtype: bool
+        """
+        try:
+            view_class = self.get_endpoint_class(endpoint)
+
+            # Reject unauthenticated users in case view requires authentication.
+            if view_class.authentication:
+                if not flask_login.current_user.is_authenticated:
+                    return False
+
+            # Check view authorization rules.
+            if view_class.authorization:
+                for auth_rule in view_class.authorization:
+                    if not auth_rule.can():
+                        return False
+
+            # Check item action authorization callback, if exists.
+            if hasattr(view_class, 'authorize_item_action'):
+                if not view_class.authorize_item_action(**kwargs):
+                    return False
+
+            return True
+
+        except VialException:
+            return False
+
+    def get_model(self, name):
+        """
+        Return reference to class of given model.
+
+        :param str name: Name of the model.
+        """
+        return self.config[hawat.const.CFGKEY_MODELS][name]
+
+    def get_resource(self, name):
+        """
+        Return reference to given registered resource.
+
+        :param str name: Name of the resource.
+        """
+        return self.resources[name]()
+
+    def set_resource(self, name, resource):
+        """
+        Store reference to given resource.
+
+        :param str name: Name of the resource.
+        :param resource: Resource to be registered.
+        """
+        self.resources[name] = weakref.ref(resource)
+
+    def set_infomailer(self, name, mailer):
+        """
+        Register mailer handle to be usable by different web interface components.
+
+        :param str name: Name of the informailer.
+        :param callable mailer: Mailer handle.
+        """
+        self.infomailers.setdefault(name, []).append(mailer)
+
+    def send_infomail(self, name, **kwargs):
+        """
+        Send emails through all registered infomailer handles.
+
+        :param str name: Name of the informailer.
+        :param **kwargs: Additional mailer arguments.
+        """
+        for mailer in self.infomailers[name]:
+            mailer(**kwargs)
+
     def get_csag(self, group_name):
         """
         Return list of all registered context search actions for given group name
@@ -153,13 +427,341 @@ class HawatApp(vial.app.Vial):
 
 
     def setup_app(self):
-        super().setup_app()
-
+        """
+        Perform setup of the whole application.
+        """
+        self._setup_app_logging()
+        self._setup_app_mailer()
+        self._setup_app_core()
+        self._setup_app_db()
+        self._setup_app_auth()
+        self._setup_app_acl()
+        self._setup_app_intl()
+        self._setup_app_menu()
+        self._setup_app_blueprints()
+        self._setup_app_cli()
         self._setup_app_eventdb()
 
+    def _setup_app_logging(self):
+        """
+        Setup logging to file and via email for given Vial application. Logging
+        capabilities are adjustable by application configuration.
+
+        :param vial.app.VialApp app: Vial application to be modified.
+        :return: Modified Vial application
+        :rtype: vial.app.VialApp
+        """
+        vial.log.setup_logging_default(self)
+        vial.log.setup_logging_file(self)
+        if not self.debug:
+            vial.log.setup_logging_email(self)
+
+        return self
+
+    def _setup_app_mailer(self):
+        """
+        Setup mailer service for Vial application.
+
+        :param vial.app.VialApp app: Vial application to be modified.
+        :return: Modified Vial application
+        :rtype: vial.app.VialApp
+        """
+        vial.mailer.MAILER.init_app(self)
+        self.mailer = vial.mailer.MAILER
+
+        return self
 
     def _setup_app_core(self):
-        super()._setup_app_core()
+        """
+        Setup application core for given Vial application. The application core
+        contains following features:
+
+            * Error handlers
+            * Default routes
+            * Additional custom Jinja template variables
+            * Additional custom Jinja template macros
+
+        :param vial.app.VialApp app: Vial application to be modified.
+        :return: Modified Vial application
+        :rtype: vial.app.VialApp
+        """
+        @self.errorhandler(400)
+        def eh_badrequest(err):  # pylint: disable=locally-disabled,unused-variable
+            """Flask error handler to be called to service HTTP 400 error."""
+            flask.current_app.logger.critical(
+                "BAD REQUEST\n\nRequest: %s\nTraceback:\n%s",
+                flask.request.full_path,
+                ''.join(
+                    traceback.TracebackException(
+                        *sys.exc_info()
+                    ).format()
+                )
+            )
+            return vial.errors.error_handler_switch(400, err)
+
+        @self.errorhandler(403)
+        def eh_forbidden(err):  # pylint: disable=locally-disabled,unused-variable
+            """Flask error handler to be called to service HTTP 403 error."""
+            return vial.errors.error_handler_switch(403, err)
+
+        @self.errorhandler(404)
+        def eh_page_not_found(err):  # pylint: disable=locally-disabled,unused-variable
+            """Flask error handler to be called to service HTTP 404 error."""
+            return vial.errors.error_handler_switch(404, err)
+
+        @self.errorhandler(405)
+        def eh_method_not_allowed(err):  # pylint: disable=locally-disabled,unused-variable
+            """Flask error handler to be called to service HTTP 405 error."""
+            return vial.errors.error_handler_switch(405, err)
+
+        @self.errorhandler(410)
+        def eh_gone(err):  # pylint: disable=locally-disabled,unused-variable
+            """Flask error handler to be called to service HTTP 410 error."""
+            return vial.errors.error_handler_switch(410, err)
+
+        @self.errorhandler(500)
+        def eh_internal_server_error(err):  # pylint: disable=locally-disabled,unused-variable
+            """Flask error handler to be called to service HTTP 500 error."""
+            flask.current_app.logger.critical(
+                "INTERNAL SERVER ERROR\n\nRequest: %s\nTraceback:\n%s",
+                flask.request.full_path,
+                ''.join(
+                    traceback.TracebackException(
+                        *sys.exc_info()
+                    ).format()
+                ),
+            )
+            return vial.errors.error_handler_switch(500, err)
+
+        @self.before_request
+        def before_request():  # pylint: disable=locally-disabled,unused-variable
+            """
+            Use Flask`s :py:func:`flask.Flask.before_request` hook for performing
+            various usefull tasks before each request.
+            """
+            flask.g.requeststart = datetime.datetime.utcnow()
+
+        @self.context_processor
+        def jinja_inject_variables():  # pylint: disable=locally-disabled,unused-variable
+            """
+            Inject additional variables into Jinja2 global template namespace.
+            """
+            return dict(
+                vial_appname           = flask.current_app.config['APPLICATION_NAME'],
+                vial_appid             = flask.current_app.config['APPLICATION_ID'],
+                vial_current_app       = flask.current_app,
+                vial_current_menu_main = flask.current_app.menu_main,
+                vial_current_menu_auth = flask.current_app.menu_auth,
+                vial_current_menu_anon = flask.current_app.menu_anon,
+                vial_current_view      = self.get_endpoint_class(flask.request.endpoint, True),
+                vial_logger            = flask.current_app.logger,
+                vial_cdt_utc           = datetime.datetime.utcnow(),
+                vial_cdt_local         = datetime.datetime.now(),
+            )
+
+        @self.context_processor
+        def jinja2_inject_functions():  # pylint: disable=locally-disabled,unused-variable,too-many-locals
+            """
+            Register additional helpers into Jinja2 global template namespace.
+            """
+            def get_modules_dict():
+                """
+                Return dictionary of all registered application pluggable modules.
+                """
+                return flask.current_app.blueprints
+
+            def get_endpoints_dict():
+                """
+                Return dictionary of all registered application view endpoints.
+                """
+                return { k: v.view_class for k, v in flask.current_app.view_functions.items() if hasattr(v, 'view_class') }
+
+            def get_endpoint_class(endpoint, quiet = False):
+                """
+                Return class reference to given view endpoint.
+
+                :param str endpoint: Name of the view endpoint.
+                :param bool quiet: Suppress the exception in case given endpoint does not exist.
+                """
+                return self.get_endpoint_class(endpoint, quiet)
+
+            def check_endpoint_exists(endpoint):
+                """
+                Check, that given application view endpoint exists and is registered within
+                the application.
+
+                :param str endpoint: Name of the view endpoint.
+                :return: ``True`` in case endpoint exists, ``False`` otherwise.
+                :rtype: bool
+                """
+                return endpoint in self.view_functions
+
+            def get_icon(icon_name, default_icon = 'missing-icon'):
+                """
+                Get HTML icon markup for given icon.
+
+                :param str icon_name: Name of the icon.
+                :param str default_icon: Name of the default icon.
+                :return: Icon including HTML markup.
+                :rtype: flask.Markup
+                """
+                return flask.Markup(
+                    self.config.get('ICONS').get(
+                        icon_name,
+                        self.config.get('ICONS').get(default_icon)
+                    )
+                )
+
+            def get_module_icon(endpoint, default_icon = 'missing-icon'):
+                """
+                Get HTML icon markup for parent module of given view endpoint.
+
+                :param str endpoint: Name of the view endpoint.
+                :param str default_icon: Name of the default icon.
+                :return: Icon including HTML markup.
+                :rtype: flask.Markup
+                """
+                return flask.Markup(
+                    self.config.get('ICONS').get(
+                        self.get_endpoint_class(endpoint).module_ref().get_module_icon(),
+                        self.config.get('ICONS').get(default_icon)
+                    )
+                )
+
+            def get_endpoint_icon(endpoint, default_icon = 'missing-icon'):
+                """
+                Get HTML icon markup for given view endpoint.
+
+                :param str endpoint: Name of the view endpoint.
+                :return: Icon including HTML markup.
+                :rtype: flask.Markup
+                """
+                return flask.Markup(
+                    self.config.get('ICONS').get(
+                        self.get_endpoint_class(endpoint).get_view_icon(),
+                        self.config.get('ICONS').get(default_icon)
+                    )
+                )
+
+            def get_country_flag(country):
+                """
+                Get URL to static country flag file.
+
+                :param str country: Name of the icon.
+                :return: Country including HTML markup.
+                :rtype: flask.Markup
+                """
+                if not hawat.const.CRE_COUNTRY_CODE.match(country):
+                    return get_icon('flag')
+
+                return flask.Markup(
+                    '<img src="{}">'.format(
+                        flask.url_for(
+                            'static',
+                            filename = 'images/country-flags/flags-iso/shiny/16/{}.png'.format(
+                                country.upper()
+                            )
+                        )
+                    )
+                )
+
+            def include_raw(filename):
+                """
+                Include given file in raw form directly into the generated content.
+                This may be usefull for example for including JavaScript files
+                directly into the HTML page.
+                """
+                return jinja2.Markup(
+                    self.jinja_loader.get_source(self.jinja_env, filename)[0]
+                )
+
+            return dict(
+                get_modules_dict      = get_modules_dict,
+                get_endpoints_dict    = get_endpoints_dict,
+                get_endpoint_class    = get_endpoint_class,
+                check_endpoint_exists = check_endpoint_exists,
+
+                get_icon          = get_icon,
+                get_module_icon   = get_module_icon,
+                get_endpoint_icon = get_endpoint_icon,
+                get_country_flag  = get_country_flag,
+
+                get_timedelta       = vial.utils.get_timedelta,
+                get_datetime_utc    = vial.utils.get_datetime_utc,
+                get_datetime_local  = vial.utils.get_datetime_local,
+                parse_datetime      = vial.utils.parse_datetime,
+
+                get_datetime_window = vial.view.mixin.VialUtils.get_datetime_window,
+
+                check_file_exists = vial.utils.check_file_exists,
+
+                in_query_params       = vial.utils.in_query_params,
+                generate_query_params = vial.utils.generate_query_params,
+
+                current_datetime_utc = datetime.datetime.utcnow(),
+
+                include_raw         = include_raw,
+                json_to_yaml        = vial.utils.json_to_yaml,
+                get_uuid4           = vial.utils.get_uuid4,
+                load_json_from_file = vial.utils.load_json_from_file,
+                make_copy_deep      = vial.utils.make_copy_deep
+            )
+
+        @self.template_filter('tojson_pretty')
+        def to_pretty_json(value):
+            return json.dumps(
+                value,
+                sort_keys = True,
+                indent = 4
+            )
+
+        class VialJSONEncoder(flask.json.JSONEncoder):
+            """
+            Custom JSON encoder for converting anything into JSON strings.
+            """
+            def default(self, obj):  # pylint: disable=locally-disabled,method-hidden,arguments-differ
+                try:
+                    if isinstance(obj, datetime.datetime):
+                        return obj.isoformat() + 'Z'
+                except:  # pylint: disable=locally-disabled,bare-except
+                    pass
+                try:
+                    return obj.to_dict()
+                except:  # pylint: disable=locally-disabled,bare-except
+                    pass
+                try:
+                    return str(obj)
+                except:  # pylint: disable=locally-disabled,bare-except
+                    pass
+                return flask.json.JSONEncoder.default(self, obj)
+
+        self.json_encoder = VialJSONEncoder
+
+        @self.route('/app-main.js')
+        def mainjs():  # pylint: disable=locally-disabled,unused-variable
+            """
+            Default route for main application JavaScript file.
+            """
+            return flask.make_response(
+                flask.render_template('app-main.js'),
+                200,
+                {'Content-Type': 'text/javascript'}
+            )
+
+        # Initialize JSGlue plugin for using `flask.url_for()` method in JavaScript.
+        #jsglue = flask_jsglue.JSGlue()
+        jsglue = vial.jsglue.JSGlue()
+        jsglue.init_app(self)
+
+        @self.template_filter()
+        def pprint_item(item):  # pylint: disable=locally-disabled,unused-variable
+            """
+            Custom Jinja2 filter for full object attribute dump/pretty-print.
+            """
+            res = []
+            for key in dir(item):
+                res.append('%r: %r' % (key, getattr(item, key)))
+            return '\n'.join(res)
 
         @self.context_processor
         def jinja_inject_variables():  # pylint: disable=locally-disabled,unused-variable
@@ -240,7 +842,30 @@ class HawatApp(vial.app.Vial):
         return self
 
     def _setup_app_db(self):
-        super()._setup_app_db()
+        """
+        Setup application database service for given Vial application.
+
+        :param vial.app.VialApp app: Vial application to be modified.
+        :return: Modified Vial application
+        :rtype: vial.app.VialApp
+        """
+        dbh = vial.db.db_setup(**self.config['SQLALCHEMY_SETUP_ARGS'])
+        dbh.init_app(self)
+
+        # Initialize database migration service and register it among the application
+        # resources for possible future use.
+        migrate = flask_migrate.Migrate(
+            app       = self,
+            db        = dbh,
+            directory = self.config['MIGRATE_DIRECTORY']
+        )
+        self.set_resource(hawat.const.RESOURCE_MIGRATE, migrate)
+
+        self.logger.debug(
+            "Connected to database via SQLAlchemy ({})".format(
+                self.config['SQLALCHEMY_DATABASE_URI']
+            )
+        )
 
         class StorageService:  # pylint: disable=locally-disabled,too-few-public-methods
             """
@@ -279,6 +904,290 @@ class HawatApp(vial.app.Vial):
 
         mentat.services.sqlstorage.set_manager(StorageServiceManager())
 
+        return self
+
+    def _setup_app_auth(self):
+        """
+        Setup application authentication features.
+
+        :param vial.app.VialApp app: Vial application to be modified.
+        :return: Modified Vial application
+        :rtype: vial.app.VialApp
+        """
+
+        lim = flask_login.LoginManager()
+        lim.init_app(self)
+        lim.login_view = self.config['ENDPOINT_LOGIN']
+        lim.login_message = flask_babel.gettext("Please log in to access this page.")
+        lim.login_message_category = self.config['LOGIN_MSGCAT']
+
+        self.set_resource(hawat.const.RESOURCE_LOGIN_MANAGER, lim)
+
+        @lim.user_loader
+        def load_user(user_id):  # pylint: disable=locally-disabled,unused-variable
+            """
+            Flask-Login callback for loading current user`s data.
+            """
+            user_model = self.get_model(hawat.const.MODEL_USER)
+            return vial.db.db_get().session.query(user_model).filter(user_model.id == user_id).one_or_none()
+
+        @self.route('/logout')
+        @flask_login.login_required
+        def logout():  # pylint: disable=locally-disabled,unused-variable
+            """
+            Flask-Login callback for logging out current user.
+            """
+            flask.current_app.logger.info(
+                "User '{}' just logged out.".format(
+                    str(flask_login.current_user)
+                )
+            )
+            flask_login.logout_user()
+            flask.flash(
+                flask_babel.gettext('You have been successfully logged out.'),
+                hawat.const.FLASH_SUCCESS
+            )
+
+            # Remove session keys set by Flask-Principal.
+            for key in ('identity.name', 'identity.auth_type'):
+                flask.session.pop(key, None)
+
+            # Tell Flask-Principal the identity changed.
+            flask_principal.identity_changed.send(
+                flask.current_app._get_current_object(),  # pylint: disable=locally-disabled,protected-access
+                identity = flask_principal.AnonymousIdentity()
+            )
+
+            # Force user to index page.
+            return flask.redirect(
+                flask.url_for(
+                    flask.current_app.config['ENDPOINT_LOGOUT_REDIRECT']
+                )
+            )
+
+        return self
+
+    def _setup_app_acl(self):
+        """
+        Setup application ACL features.
+
+        :param vial.app.VialApp app: Vial application to be modified.
+        :return: Modified Vial application
+        :rtype: vial.app.VialApp
+        """
+        fpp = flask_principal.Principal(self, skip_static = True)
+        self.set_resource(hawat.const.RESOURCE_PRINCIPAL, fpp)
+
+        @flask_principal.identity_loaded.connect_via(self)
+        def on_identity_loaded(sender, identity):  # pylint: disable=locally-disabled,unused-variable,unused-argument
+            """
+            Flask-Principal callback for populating user identity object after login.
+            """
+            # Set the identity user object.
+            identity.user = flask_login.current_user
+
+            if not flask_login.current_user.is_authenticated:
+                flask.current_app.logger.debug(
+                    "Loaded ACL identity for anonymous user '{}'.".format(
+                        str(flask_login.current_user)
+                    )
+                )
+                return
+            flask.current_app.logger.debug(
+                "Loading ACL identity for user '{}'.".format(
+                    str(flask_login.current_user)
+                )
+            )
+
+            # Add the UserNeed to the identity.
+            if hasattr(flask_login.current_user, 'get_id'):
+                identity.provides.add(
+                    flask_principal.UserNeed(flask_login.current_user.id)
+                )
+
+            # Assuming the User model has a list of roles, update the
+            # identity with the roles that the user provides.
+            if hasattr(flask_login.current_user, 'roles'):
+                for role in flask_login.current_user.roles:
+                    identity.provides.add(
+                        flask_principal.RoleNeed(role)
+                    )
+
+            # Assuming the User model has a list of group memberships, update the
+            # identity with the groups that the user is member of.
+            if hasattr(flask_login.current_user, 'memberships'):
+                for group in flask_login.current_user.memberships:
+                    identity.provides.add(
+                        vial.acl.MembershipNeed(group.id)
+                    )
+
+            # Assuming the User model has a list of group managements, update the
+            # identity with the groups that the user is manager of.
+            if hasattr(flask_login.current_user, 'managements'):
+                for group in flask_login.current_user.managements:
+                    identity.provides.add(
+                        vial.acl.ManagementNeed(group.id)
+                    )
+
+        @self.context_processor
+        def utility_acl_processor():  # pylint: disable=locally-disabled,unused-variable
+            """
+            Register additional helpers related to authorization into Jinja global
+            namespace to enable them within the templates.
+            """
+            def can_access_endpoint(endpoint, item = None):
+                """
+                Check if currently logged-in user can access given endpoint/view.
+
+                :param str endpoint: Name of the application endpoint.
+                :param item: Optional item for additional validations.
+                :return: ``True`` in case user can access the endpoint, ``False`` otherwise.
+                :rtype: bool
+                """
+                return flask.current_app.can_access_endpoint(endpoint, item = item)
+
+            def permission_can(permission_name):
+                """
+                Manually check currently logged-in user for given permission.
+
+                :param str permission_name: Name of the permission.
+                :return: Check result.
+                :rtype: bool
+                """
+                return vial.acl.PERMISSIONS[permission_name].can()
+
+            def is_it_me(item):
+                """
+                Check if given user account is mine.
+                """
+                return item.id == flask_login.current_user.id
+
+            return dict(
+                can_access_endpoint = can_access_endpoint,
+                permission_can      = permission_can,
+                is_it_me            = is_it_me
+            )
+
+        return self
+
+    def _setup_app_intl(self):
+        """
+        Setup application`s internationalization sybsystem.
+
+        :param vial.app.VialApp app: Vial application to be modified.
+        :return: Modified Vial application
+        :rtype: vial.app.VialApp
+        """
+        vial.intl.BABEL.init_app(self)
+        self.set_resource(hawat.const.RESOURCE_BABEL, vial.intl.BABEL)
+
+        @self.route('/locale/<code>')
+        def locale(code):  # pylint: disable=locally-disabled,unused-variable
+            """
+            Application route providing users with the option of changing locale.
+            """
+            if code not in flask.current_app.config['SUPPORTED_LOCALES']:
+                return flask.abort(404)
+
+            if flask_login.current_user.is_authenticated:
+                flask_login.current_user.locale = code
+                # Make sure current user is in SQLAlchemy session. Turns out, this
+                # step is not necessary and current user is already in session,
+                # because it was fetched from database few moments ago.
+                #vial.db.db_session().add(flask_login.current_user)
+                vial.db.db_session().commit()
+
+            flask.session['locale'] = code
+            flask_babel.refresh()
+
+            flask.flash(
+                flask.Markup(flask_babel.gettext(
+                    'Locale was succesfully changed to <strong>%(lcln)s (%(lclc)s)</strong>.',
+                    lclc = code,
+                    lcln = flask.current_app.config['SUPPORTED_LOCALES'][code]
+                )),
+                hawat.const.FLASH_SUCCESS
+            )
+
+            # Redirect user back to original page.
+            return flask.redirect(
+                vial.forms.get_redirect_target(
+                    default_url = flask.url_for(
+                        flask.current_app.config['ENDPOINT_HOME']
+                    )
+                )
+            )
+
+        @self.before_request
+        def before_request():  # pylint: disable=locally-disabled,unused-variable
+            """
+            Use Flask`s :py:func:`flask.Flask.before_request` hook for storing
+            currently selected locale and timezone to request`s session storage.
+            """
+            if 'locale' not in flask.session:
+                flask.session['locale'] = vial.intl.get_locale()
+            if 'timezone' not in flask.session:
+                flask.session['timezone'] = vial.intl.get_timezone()
+
+        @self.context_processor
+        def utility_processor():  # pylint: disable=locally-disabled,unused-variable
+            """
+            Register additional internationalization helpers into Jinja global namespace.
+            """
+
+            return dict(
+                babel_get_locale         = vial.intl.get_locale,
+                babel_get_timezone       = vial.intl.get_timezone,
+                babel_format_datetime    = flask_babel.format_datetime,
+                babel_format_date        = flask_babel.format_date,
+                babel_format_time        = flask_babel.format_time,
+                babel_format_timedelta   = flask_babel.format_timedelta,
+                babel_format_decimal     = flask_babel.format_decimal,
+                babel_format_percent     = flask_babel.format_percent,
+                babel_format_bytes       = vial.intl.babel_format_bytes,
+                babel_translate_locale   = vial.intl.babel_translate_locale,
+                babel_language_in_locale = vial.intl.babel_language_in_locale
+            )
+
+        return self
+
+    def _setup_app_menu(self):
+        """
+        Setup default application menu skeleton.
+
+        :param vial.app.VialApp app: Vial application to be modified.
+        :return: Modified Vial application
+        :rtype: vial.app.VialApp
+        """
+        for entry in self.config[hawat.const.CFGKEY_MENU_MAIN_SKELETON]:
+            self.menu_main.add_entry(**entry)
+
+        return self
+
+    def _setup_app_blueprints(self):
+        """
+        Setup application blueprints.
+
+        :param vial.app.VialApp app: Vial application to be modified.
+        :return: Modified Vial application
+        :rtype: vial.app.VialApp
+        """
+        self.register_blueprints()
+
+        return self
+
+    def _setup_app_cli(app):
+        """
+        Setup application command line interface.
+
+        :param vial.app.VialApp app: Vial application to be modified.
+        :return: Modified Vial application
+        :rtype: vial.app.VialApp
+        """
+        vial.command.setup_cli(app)
+
+        return app
+
     def _setup_app_eventdb(self):
         """
         Setup application database service for given Vial application.
@@ -292,6 +1201,81 @@ class HawatApp(vial.app.Vial):
 
         return self
 
+class HawatBlueprint(flask.Blueprint):
+    """
+    Custom implementation of :py:class:`flask.Blueprint` class. This class extends
+    the capabilities of the base class with additional features:
+
+        * Support for better integration into application and registration of view classes.
+        * Support for custom tweaking of application object.
+        * Support for custom style of authentication and authorization decorators
+    """
+    def __init__(self, name, import_name, **kwargs):
+        super().__init__(name, import_name, **kwargs)
+
+        self.sign_ins     = {}
+        self.sign_ups     = {}
+
+    @classmethod
+    def get_module_title(cls):
+        """
+        Get human readable name for this blueprint/module.
+
+        :return: Name (short summary) of the blueprint/module.
+        :rtype: str
+        """
+        raise NotImplementedError()
+
+    def get_module_icon(self):
+        """
+        Return icon name for the module. Given name will be used as index to
+        built-in icon registry.
+
+        :return: Icon for the module.
+        :rtype: str
+        """
+        return 'module-{}'.format(self.name).replace('_', '-')
+
+    def register_app(self, app):  # pylint: disable=locally-disabled,no-self-use,unused-argument
+        """
+        *Hook method:* Custom callback, which will be called from
+        :py:func:`vial.app.Vial.register_blueprint` method and which can
+        perform additional tweaking of Vial application object.
+
+        :param vial.app.Vial app: Application object.
+        """
+        return
+
+    def register_view_class(self, view_class, route_spec):
+        """
+        Register given view class into the internal blueprint registry.
+
+        :param vial.view.BaseView view_class: View class (not instance!)
+        :param str route_spec: Routing information for the view.
+        """
+        view_class.module_ref  = weakref.ref(self)
+        view_class.module_name = self.name
+
+        # Obtain view function.
+        view_func = view_class.as_view(view_class.get_view_name())
+
+        # Apply authorization decorators (if requested).
+        if view_class.authorization:
+            for auth in view_class.authorization:
+                view_func = auth.require(403)(view_func)
+
+        # Apply authentication decorators (if requested).
+        if view_class.authentication:
+            view_func = flask_login.login_required(view_func)
+
+        # Register endpoint to the application.
+        self.add_url_rule(route_spec, view_func = view_func)
+
+        # Register SIGN IN and SIGN UP views to enable further special handling.
+        if hasattr(view_class, 'is_sign_in') and view_class.is_sign_in:
+            self.sign_ins[view_class.get_view_endpoint()] = view_class
+        if hasattr(view_class, 'is_sign_up') and view_class.is_sign_up:
+            self.sign_ups[view_class.get_view_endpoint()] = view_class
 
 class PsycopgMixin:
     """
diff --git a/lib/hawat/blueprints/auth/__init__.py b/lib/hawat/blueprints/auth/__init__.py
index a101cbd89..bd3adcbca 100644
--- a/lib/hawat/blueprints/auth/__init__.py
+++ b/lib/hawat/blueprints/auth/__init__.py
@@ -100,7 +100,7 @@ class RegisterView(HTMLMixin, SimpleView):
 #-------------------------------------------------------------------------------
 
 
-class AuthBlueprint(VialBlueprint):
+class AuthBlueprint(HawatBlueprint):
     """Pluggable module - authentication and registration directional service (*auth*)."""
 
     @classmethod
@@ -129,8 +129,8 @@ class AuthBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`hawat.Vial` and factory function. This function
-    must return a valid instance of :py:class:`hawat.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/auth_dev/test/__init__.py b/lib/hawat/blueprints/auth_dev/test/__init__.py
index 370db18fe..191328d9f 100644
--- a/lib/hawat/blueprints/auth_dev/test/__init__.py
+++ b/lib/hawat/blueprints/auth_dev/test/__init__.py
@@ -16,9 +16,9 @@ Unit tests for :py:mod:`hawat.blueprints.auth_dev`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.test.fixtures
-import vial.db
+import hawat.test
+import hawat.test.fixtures
+import hawat.db
 from hawat.test import RegistrationHawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
diff --git a/lib/hawat/blueprints/auth_env/__init__.py b/lib/hawat/blueprints/auth_env/__init__.py
index 192253a6f..6ea0425d0 100644
--- a/lib/hawat/blueprints/auth_env/__init__.py
+++ b/lib/hawat/blueprints/auth_env/__init__.py
@@ -74,17 +74,24 @@ __author__ = "Jan Mach <jan.mach@cesnet.cz>"
 __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
 
 
+
 import flask
-from flask_babel import gettext
+from flask_babel import gettext, lazy_gettext
 
 import hawat.const
-import vial.forms
-import vial.db
-import vial.blueprints.auth_env
-from vial.blueprints.auth_env import BLUEPRINT_NAME, LoginView
+import hawat.forms
+import hawat.db
+from hawat.app import HawatBlueprint
+from hawat.view import BaseLoginView, BaseRegisterView
+from hawat.view.mixin import HTMLMixin, SQLAlchemyMixin
+
 from hawat.blueprints.auth_env.forms import RegisterUserAccountForm
 
 
+BLUEPRINT_NAME = 'auth_env'
+"""Name of the blueprint as module global constant."""
+
+
 class RegistrationException(Exception):
     """
     Exception describing problems with new user account registration.
@@ -106,11 +113,60 @@ def get_login_from_environment():
         flask.request.environ.get('REMOTE_USER', None)
     )
 
+class LoginView(HTMLMixin, SQLAlchemyMixin, BaseLoginView):
+    """
+    View responsible for user login via application environment.
+    """
+    methods = ['GET']
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return lazy_gettext('Environment login')
+
+    @classmethod
+    def get_menu_title(cls, **kwargs):
+        return lazy_gettext('Login (env)')
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def search_by(self):
+        return self.dbmodel.login
+
+    def get_user_login(self):
+        user_login = get_login_from_environment()
+        if not user_login:
+            self.flash(
+                gettext('User login was not received, unable to perform login process.'),
+                hawat.const.FLASH_FAILURE
+            )
+            self.abort(403)
+        return user_login
 
-class RegisterView(vial.blueprints.auth_env.RegisterView):
+
+class RegisterView(HTMLMixin, SQLAlchemyMixin, BaseRegisterView):
     """
     View responsible for registering new user account into application.
     """
+    methods = ['GET', 'POST']
+
+    @classmethod
+    def get_menu_title(cls, **kwargs):
+        return lazy_gettext('Register (env)')
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return lazy_gettext('User account registration (env)')
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
 
     def get_user_from_env(self):
         """
@@ -192,12 +248,27 @@ class RegisterView(vial.blueprints.auth_env.RegisterView):
         )
 
 
+    def dispatch_request(self):
+        """
+        Mandatory interface required by the :py:func:`flask.views.View.dispatch_request`.
+        Will be called by the *Flask* framework to service the request.
+        """
+        self.response_context.update(
+            apacheenv = flask.request.environ
+        )
+        return super().dispatch_request()
+
+
 #-------------------------------------------------------------------------------
 
 
-class EnvAuthBlueprint(vial.blueprints.auth_env.EnvAuthBlueprint):
+class EnvAuthBlueprint(HawatBlueprint):
     """Pluggable module - environment authentication service (*auth_env*)."""
 
+    @classmethod
+    def get_module_title(cls):
+        return lazy_gettext('Environment authentication service')
+
     def register_app(self, app):
         app.set_infomailer('auth_env.register', RegisterView.inform_admins)
         app.set_infomailer('auth_env.register', RegisterView.inform_managers)
@@ -209,8 +280,8 @@ class EnvAuthBlueprint(vial.blueprints.auth_env.EnvAuthBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/auth_env/forms.py b/lib/hawat/blueprints/auth_env/forms.py
index 2013422aa..82fdf2df5 100644
--- a/lib/hawat/blueprints/auth_env/forms.py
+++ b/lib/hawat/blueprints/auth_env/forms.py
@@ -22,7 +22,7 @@ from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField
 
 from flask_babel import lazy_gettext
 
-from vial.forms import get_available_groups
+from hawat.forms import get_available_groups
 
 from hawat.blueprints.users.forms import BaseUserAccountForm
 
diff --git a/lib/hawat/blueprints/auth_env/test/__init__.py b/lib/hawat/blueprints/auth_env/test/__init__.py
index 661cc63a5..beb387cb6 100644
--- a/lib/hawat/blueprints/auth_env/test/__init__.py
+++ b/lib/hawat/blueprints/auth_env/test/__init__.py
@@ -16,9 +16,9 @@ Unit tests for :py:mod:`hawat.blueprints.auth_env`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.test.fixtures
-import vial.db
+import hawat.test
+import hawat.test.fixtures
+import hawat.db
 from hawat.test import RegistrationHawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
diff --git a/lib/hawat/blueprints/auth_pwd/__init__.py b/lib/hawat/blueprints/auth_pwd/__init__.py
index b6a17dd9f..cff95837b 100644
--- a/lib/hawat/blueprints/auth_pwd/__init__.py
+++ b/lib/hawat/blueprints/auth_pwd/__init__.py
@@ -29,19 +29,83 @@ __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea
 
 
 import flask
+from flask_babel import lazy_gettext
 
 import hawat.const
-import vial.forms
-import vial.blueprints.auth_pwd
-from vial.blueprints.auth_pwd import BLUEPRINT_NAME, LoginView, PwdAuthBlueprint
-from hawat.blueprints.auth_pwd.forms import RegisterUserAccountForm
+import hawat.forms
+from hawat.app import HawatBlueprint
+from hawat.view import BaseLoginView, BaseRegisterView
+from hawat.view.mixin import HTMLMixin, SQLAlchemyMixin
 
+from hawat.blueprints.auth_pwd.forms import LoginForm, RegisterUserAccountForm
 
 
-class RegisterView(vial.blueprints.auth_pwd.RegisterView):
+BLUEPRINT_NAME = 'auth_pwd'
+"""Name of the blueprint as module global constant."""
+
+
+class LoginView(HTMLMixin, SQLAlchemyMixin, BaseLoginView):
     """
     View enabling classical password login.
     """
+    methods = ['GET', 'POST']
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return lazy_gettext('Password login')
+
+    @classmethod
+    def get_menu_title(cls, **kwargs):
+        return lazy_gettext('Login (pwd)')
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def search_by(self):
+        return self.dbmodel.login
+
+    def get_user_login(self):
+        form = LoginForm()
+        self.response_context.update(
+            form = form
+        )
+        if form.validate_on_submit():
+            return form.login.data
+        return None
+
+    def authenticate_user(self, user):
+        return user.check_password(
+            self.response_context['form'].password.data
+        )
+
+
+class RegisterView(HTMLMixin, SQLAlchemyMixin, BaseRegisterView):
+    """
+    View enabling classical password login.
+    """
+    methods = ['GET', 'POST']
+
+    @classmethod
+    def get_menu_title(cls, **kwargs):
+        return lazy_gettext('Register (pwd)')
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return lazy_gettext('User account registration (pwd)')
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @property
+    def search_by(self):
+        return self.dbmodel.login
 
     @staticmethod
     def get_item_form(item):
@@ -52,14 +116,29 @@ class RegisterView(vial.blueprints.auth_pwd.RegisterView):
             choices_locales = locales
         )
 
+    def do_before_action(self, item):  # pylint: disable=locally-disabled,no-self-use,unused-argument
+        super().do_before_action(item)
+        item.set_password(item.password)
+
+
+#-------------------------------------------------------------------------------
+
+
+class PwdAuthBlueprint(HawatBlueprint):
+    """Pluggable module - classical authentication service (*auth_pwd*)."""
+
+    @classmethod
+    def get_module_title(cls):
+        return lazy_gettext('Password authentication service')
+
 
 #-------------------------------------------------------------------------------
 
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/auth_pwd/forms.py b/lib/hawat/blueprints/auth_pwd/forms.py
index 9d8ac9972..ec553ce68 100644
--- a/lib/hawat/blueprints/auth_pwd/forms.py
+++ b/lib/hawat/blueprints/auth_pwd/forms.py
@@ -17,13 +17,38 @@ __author__ = "Honza Mach <honza.mach.ml@gmail.com>"
 
 
 import wtforms
+import flask_wtf
 from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField
 from flask_babel import lazy_gettext
 
-from vial.forms import check_login, check_unique_login, get_available_groups
+from hawat.forms import check_login, check_unique_login, get_available_groups
 from hawat.blueprints.users.forms import BaseUserAccountForm
 
 
+class LoginForm(flask_wtf.FlaskForm):
+    """
+    Class representing classical password authentication login form.
+    """
+    login = wtforms.StringField(
+        lazy_gettext('Login:'),
+        validators = [
+            wtforms.validators.DataRequired(),
+            wtforms.validators.Length(min = 3, max = 50),
+            check_login
+        ]
+    )
+    password = wtforms.PasswordField(
+        lazy_gettext('Password:'),
+        validators = [
+            wtforms.validators.DataRequired(),
+            wtforms.validators.Length(min = 8),
+        ]
+    )
+    submit = wtforms.SubmitField(
+        lazy_gettext('Login')
+    )
+
+
 class RegisterUserAccountForm(BaseUserAccountForm):
     """
     Class representing classical account registration form.
diff --git a/lib/hawat/blueprints/auth_pwd/test/__init__.py b/lib/hawat/blueprints/auth_pwd/test/__init__.py
index 1b838fce3..da53a34e9 100644
--- a/lib/hawat/blueprints/auth_pwd/test/__init__.py
+++ b/lib/hawat/blueprints/auth_pwd/test/__init__.py
@@ -16,9 +16,9 @@ Unit tests for :py:mod:`hawat.blueprints.auth_pwd`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.test.fixtures
-import vial.db
+import hawat.test
+import hawat.test.fixtures
+import hawat.db
 from hawat.test import RegistrationHawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
diff --git a/lib/vial/blueprints/changelogs/__init__.py b/lib/hawat/blueprints/changelogs/__init__.py
similarity index 91%
rename from lib/vial/blueprints/changelogs/__init__.py
rename to lib/hawat/blueprints/changelogs/__init__.py
index f1109f6ce..7cdd3fcc9 100644
--- a/lib/vial/blueprints/changelogs/__init__.py
+++ b/lib/hawat/blueprints/changelogs/__init__.py
@@ -13,11 +13,11 @@ This pluggable module provides access to item changelogs.
 import flask
 from flask_babel import lazy_gettext
 
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import BaseSearchView, ItemShowView
-from vial.view.mixin import HTMLMixin, SQLAlchemyMixin
-from vial.blueprints.changelogs.forms import ItemChangeLogSearchForm
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import BaseSearchView, ItemShowView
+from hawat.view.mixin import HTMLMixin, SQLAlchemyMixin
+from hawat.blueprints.changelogs.forms import ItemChangeLogSearchForm
 
 
 BLUEPRINT_NAME = 'changelogs'
@@ -32,7 +32,7 @@ class SearchView(HTMLMixin, SQLAlchemyMixin, BaseSearchView):  # pylint: disable
 
     authentication = True
 
-    authorization = [vial.acl.PERMISSION_POWER]
+    authorization = [hawat.acl.PERMISSION_POWER]
 
     @classmethod
     def get_menu_title(cls, **kwargs):
@@ -124,7 +124,7 @@ class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView):
 
     authentication = True
 
-    authorization = [vial.acl.PERMISSION_POWER]
+    authorization = [hawat.acl.PERMISSION_POWER]
 
     @classmethod
     def get_menu_title(cls, **kwargs):
@@ -149,7 +149,7 @@ class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView):
 
     @classmethod
     def get_breadcrumbs_menu(cls):  # pylint: disable=locally-disabled,unused-argument
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'home',
@@ -171,7 +171,7 @@ class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView):
 #-------------------------------------------------------------------------------
 
 
-class ItemChangeLogsBlueprint(VialBlueprint):
+class ItemChangeLogsBlueprint(HawatBlueprint):
     """Pluggable module - item changelog record management (*changelogs*)."""
 
     @classmethod
@@ -192,8 +192,8 @@ class ItemChangeLogsBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/vial/blueprints/changelogs/forms.py b/lib/hawat/blueprints/changelogs/forms.py
similarity index 84%
rename from lib/vial/blueprints/changelogs/forms.py
rename to lib/hawat/blueprints/changelogs/forms.py
index f3093ed0b..72ea33776 100644
--- a/lib/vial/blueprints/changelogs/forms.py
+++ b/lib/hawat/blueprints/changelogs/forms.py
@@ -17,8 +17,8 @@ import flask_wtf
 from flask_babel import lazy_gettext
 
 import hawat.const
-import vial.forms
-import vial.db
+import hawat.forms
+import hawat.db
 
 
 def get_item_operation_choices():
@@ -26,7 +26,7 @@ def get_item_operation_choices():
     Return select choices for item changelog operations.
     """
     item_changelog_model = flask.current_app.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-    operations_list = vial.db.db_query(item_changelog_model).\
+    operations_list = hawat.db.db_query(item_changelog_model).\
         distinct(item_changelog_model.operation).\
         all()
     return list(
@@ -41,7 +41,7 @@ def get_item_model_choices():
     Return select choices for item changelog item models.
     """
     item_changelog_model = flask.current_app.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-    models_list = vial.db.db_query(item_changelog_model).\
+    models_list = hawat.db.db_query(item_changelog_model).\
         distinct(item_changelog_model.model).\
         all()
     return list(
@@ -51,13 +51,13 @@ def get_item_model_choices():
         )
     )
 
-class ItemChangeLogSearchForm(vial.forms.BaseSearchForm):
+class ItemChangeLogSearchForm(hawat.forms.BaseSearchForm):
     """
     Class representing item changelog search form.
     """
     authors = QuerySelectMultipleField(
         lazy_gettext('Authors:'),
-        query_factory = vial.forms.get_available_users,
+        query_factory = hawat.forms.get_available_users,
         get_label = lambda x: '{} ({})'.format(x.fullname, x.login)
     )
     operations = wtforms.SelectMultipleField(
@@ -82,14 +82,14 @@ class ItemChangeLogSearchForm(vial.forms.BaseSearchForm):
             wtforms.validators.Optional(),
         ]
     )
-    dt_from = vial.forms.SmartDateTimeField(
+    dt_from = hawat.forms.SmartDateTimeField(
         lazy_gettext('From:'),
         validators = [
             wtforms.validators.Optional()
         ],
-        default = lambda: vial.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
+        default = lambda: hawat.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
     )
-    dt_to = vial.forms.SmartDateTimeField(
+    dt_to = hawat.forms.SmartDateTimeField(
         lazy_gettext('To:'),
         validators = [
             wtforms.validators.Optional()
@@ -120,14 +120,14 @@ class ItemChangeLogDashboardForm(flask_wtf.FlaskForm):
     """
     Class representing item changelog dashboard search form.
     """
-    dt_from = vial.forms.SmartDateTimeField(
+    dt_from = hawat.forms.SmartDateTimeField(
         lazy_gettext('From:'),
         validators = [
             wtforms.validators.Optional()
         ],
-        default = lambda: vial.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
+        default = lambda: hawat.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
     )
-    dt_to = vial.forms.SmartDateTimeField(
+    dt_to = hawat.forms.SmartDateTimeField(
         lazy_gettext('To:'),
         validators = [
             wtforms.validators.Optional()
diff --git a/lib/vial/blueprints/changelogs/templates/changelogs/search.html b/lib/hawat/blueprints/changelogs/templates/changelogs/search.html
similarity index 100%
rename from lib/vial/blueprints/changelogs/templates/changelogs/search.html
rename to lib/hawat/blueprints/changelogs/templates/changelogs/search.html
diff --git a/lib/vial/blueprints/changelogs/templates/changelogs/show.html b/lib/hawat/blueprints/changelogs/templates/changelogs/show.html
similarity index 100%
rename from lib/vial/blueprints/changelogs/templates/changelogs/show.html
rename to lib/hawat/blueprints/changelogs/templates/changelogs/show.html
diff --git a/lib/vial/blueprints/changelogs/test/__init__.py b/lib/hawat/blueprints/changelogs/test/__init__.py
similarity index 73%
rename from lib/vial/blueprints/changelogs/test/__init__.py
rename to lib/hawat/blueprints/changelogs/test/__init__.py
index 50af6dc9f..ce9e1917f 100644
--- a/lib/vial/blueprints/changelogs/test/__init__.py
+++ b/lib/hawat/blueprints/changelogs/test/__init__.py
@@ -6,7 +6,7 @@
 
 
 """
-Unit tests for :py:mod:`vial.blueprints.changelogs`.
+Unit tests for :py:mod:`hawat.blueprints.changelogs`.
 """
 
 
@@ -14,16 +14,16 @@ import sys
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
-from vial.test.runner import TestRunnerMixin
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
+from hawat.test.runner import TestRunnerMixin
 
 
 _IS_NOSE = sys.argv[0].endswith('nosetests')
 
 @unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class ChangeLogsSearchTestCase(TestRunnerMixin, VialTestCase):
+class ChangeLogsSearchTestCase(TestRunnerMixin, HawatTestCase):
     """Class for testing ``changelogs.search`` endpoint."""
 
     def _attempt_fail(self):
@@ -38,22 +38,22 @@ class ChangeLogsSearchTestCase(TestRunnerMixin, VialTestCase):
             200
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user ``user``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/hawat/blueprints/dbstatus/__init__.py b/lib/hawat/blueprints/dbstatus/__init__.py
index 2a71d6bc7..99f5ebe44 100644
--- a/lib/hawat/blueprints/dbstatus/__init__.py
+++ b/lib/hawat/blueprints/dbstatus/__init__.py
@@ -66,12 +66,12 @@ import mentat.const
 import mentat.system
 from mentat.datatype.sqldb import UserModel, GroupModel, FilterModel, SettingsReportingModel
 
-import vial.menu
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import RenderableView, SimpleView
-from vial.view.mixin import HTMLMixin, AJAXMixin, SQLAlchemyMixin
-from vial.forms import ItemActionConfirmForm
+import hawat.menu
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import RenderableView, SimpleView
+from hawat.view.mixin import HTMLMixin, AJAXMixin, SQLAlchemyMixin
+from hawat.forms import ItemActionConfirmForm
 from hawat.base import PsycopgMixin
 from hawat.base import RE_UQUERY
 
@@ -86,7 +86,7 @@ class ViewView(HTMLMixin, PsycopgMixin, SimpleView):
     """
     authentication = True
 
-    authorization = [vial.acl.PERMISSION_ADMIN]
+    authorization = [hawat.acl.PERMISSION_ADMIN]
 
     @classmethod
     def get_view_name(cls):
@@ -116,7 +116,7 @@ class ViewView(HTMLMixin, PsycopgMixin, SimpleView):
             record['user_id'] = user_id
             record['query_id'] = query_id
             if user_id not in cache:
-                cache[user_id] = vial.db.db_get().session.query(UserModel).filter(UserModel.id == int(user_id)).one_or_none()
+                cache[user_id] = hawat.db.db_get().session.query(UserModel).filter(UserModel.id == int(user_id)).one_or_none()
             record['user'] = cache[user_id]
         return result
 
@@ -145,7 +145,7 @@ class ViewView(HTMLMixin, PsycopgMixin, SimpleView):
             database_statistics_events = dbstatistics_events
         )
 
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'stop',
@@ -191,7 +191,7 @@ class MyQueriesView(HTMLMixin, PsycopgMixin, SimpleView):
             record['user_id'] = user_id
             record['query_id'] = query_id
             if user_id not in cache:
-                cache[user_id] = vial.db.db_get().session.query(UserModel).filter(UserModel.id == int(user_id)).one_or_none()
+                cache[user_id] = hawat.db.db_get().session.query(UserModel).filter(UserModel.id == int(user_id)).one_or_none()
             record['user'] = cache[user_id]
         return result
 
@@ -206,7 +206,7 @@ class MyQueriesView(HTMLMixin, PsycopgMixin, SimpleView):
             )
         )
 
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'stop',
@@ -264,7 +264,7 @@ class QueryStatusView(AJAXMixin, PsycopgMixin, RenderableView):
         Will be called by the **Flask** framework to service the request.
         """
         user_id, _ = self.parse_qname(item_id)
-        if flask_login.current_user.get_id() != user_id and not vial.acl.PERMISSION_POWER.can():
+        if flask_login.current_user.get_id() != user_id and not hawat.acl.PERMISSION_POWER.can():
             self.abort(
                 403,
                 gettext('You are not allowed to view status of this query.')
@@ -304,7 +304,7 @@ class AbstractQueryStopView(PsycopgMixin, RenderableView):  # pylint: disable=lo
     @classmethod
     def authorize_item_action(cls, **kwargs):
         user_id, _ = cls.parse_qname(kwargs['item']['query_name'])
-        return vial.acl.PERMISSION_POWER.can() or flask_login.current_user.get_id() == user_id
+        return hawat.acl.PERMISSION_POWER.can() or flask_login.current_user.get_id() == user_id
 
     @staticmethod
     def get_message_success(**kwargs):
@@ -445,7 +445,7 @@ class DashboardView(HTMLMixin, SQLAlchemyMixin, SimpleView):  # pylint: disable=
     """
     authentication = True
 
-    authorization = [vial.acl.PERMISSION_POWER]
+    authorization = [hawat.acl.PERMISSION_POWER]
 
     @classmethod
     def get_view_name(cls):
@@ -467,7 +467,7 @@ class DashboardView(HTMLMixin, SQLAlchemyMixin, SimpleView):  # pylint: disable=
 
 
     def do_before_response(self, **kwargs):
-        """*Implementation* of :py:func:`vial.view.RenderableView.do_before_response`."""
+        """*Implementation* of :py:func:`hawat.view.RenderableView.do_before_response`."""
         self.response_context['users_disabled'] = self.dbquery(UserModel).\
             filter(UserModel.enabled == False).\
             order_by(UserModel.createtime.desc()).\
@@ -538,7 +538,7 @@ class DashboardView(HTMLMixin, SQLAlchemyMixin, SimpleView):  # pylint: disable=
             order_by(SettingsReportingModel.createtime.desc()).\
             all()
 
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'show',
@@ -580,7 +580,7 @@ class DashboardView(HTMLMixin, SQLAlchemyMixin, SimpleView):  # pylint: disable=
         )
         self.response_context['context_action_menu_user'] = action_menu
 
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'show',
@@ -619,7 +619,7 @@ class DashboardView(HTMLMixin, SQLAlchemyMixin, SimpleView):  # pylint: disable=
         )
         self.response_context['context_action_menu_group'] = action_menu
 
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'show',
@@ -662,7 +662,7 @@ class DashboardView(HTMLMixin, SQLAlchemyMixin, SimpleView):  # pylint: disable=
 #-------------------------------------------------------------------------------
 
 
-class DatabaseStatusBlueprint(VialBlueprint):
+class DatabaseStatusBlueprint(HawatBlueprint):
     """Pluggable module - database status overview (*dbstatus*)."""
 
     @classmethod
@@ -695,8 +695,8 @@ class DatabaseStatusBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/dbstatus/test/__init__.py b/lib/hawat/blueprints/dbstatus/test/__init__.py
index 057250ae6..8d1a80f11 100644
--- a/lib/hawat/blueprints/dbstatus/test/__init__.py
+++ b/lib/hawat/blueprints/dbstatus/test/__init__.py
@@ -16,13 +16,13 @@ Unit tests for :py:mod:`hawat.blueprints.dbstatus`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class DBStatusViewTestCase(TestRunnerMixin, VialTestCase):
+class DBStatusViewTestCase(TestRunnerMixin, HawatTestCase):
     """Class for testing ``dbstatus.view`` endpoint."""
 
     def _attempt_fail_redirect(self):
@@ -57,28 +57,28 @@ class DBStatusViewTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail_redirect()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
 
 
-class DBStatusMyQueriesTestCase(TestRunnerMixin, VialTestCase):
+class DBStatusMyQueriesTestCase(TestRunnerMixin, HawatTestCase):
     """Class for testing ``dbstatus.queries_my`` endpoint."""
 
     def _attempt_fail(self):
@@ -105,28 +105,28 @@ class DBStatusMyQueriesTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
 
 
-class DBStatusDashboardTestCase(TestRunnerMixin, VialTestCase):
+class DBStatusDashboardTestCase(TestRunnerMixin, HawatTestCase):
     """Class for testing ``dbstatus.dashboard`` endpoint."""
 
     def _attempt_fail_redirect(self):
@@ -159,22 +159,22 @@ class DBStatusDashboardTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail_redirect()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/vial/blueprints/design_bs3/__init__.py b/lib/hawat/blueprints/design_bs3/__init__.py
similarity index 88%
rename from lib/vial/blueprints/design_bs3/__init__.py
rename to lib/hawat/blueprints/design_bs3/__init__.py
index d76ceebec..b0a050683 100644
--- a/lib/vial/blueprints/design_bs3/__init__.py
+++ b/lib/hawat/blueprints/design_bs3/__init__.py
@@ -34,7 +34,7 @@ Module content
 
 from flask_babel import lazy_gettext
 
-from vial.app import VialBlueprint
+from hawat.app import HawatBlueprint
 
 
 #
@@ -46,7 +46,7 @@ BLUEPRINT_NAME = 'design'
 #-------------------------------------------------------------------------------
 
 
-class DesignBlueprint(VialBlueprint):
+class DesignBlueprint(HawatBlueprint):
     """Pluggable module - application design and style (*design*)."""
 
     @classmethod
@@ -58,8 +58,8 @@ class DesignBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/vial/blueprints/design_bs3/templates/_layout.html b/lib/hawat/blueprints/design_bs3/templates/_layout.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/_layout.html
rename to lib/hawat/blueprints/design_bs3/templates/_layout.html
diff --git a/lib/vial/blueprints/design_bs3/templates/_layout_confirmation.html b/lib/hawat/blueprints/design_bs3/templates/_layout_confirmation.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/_layout_confirmation.html
rename to lib/hawat/blueprints/design_bs3/templates/_layout_confirmation.html
diff --git a/lib/vial/blueprints/design_bs3/templates/_layout_creatupdate.html b/lib/hawat/blueprints/design_bs3/templates/_layout_creatupdate.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/_layout_creatupdate.html
rename to lib/hawat/blueprints/design_bs3/templates/_layout_creatupdate.html
diff --git a/lib/vial/blueprints/design_bs3/templates/_layout_events_search.html b/lib/hawat/blueprints/design_bs3/templates/_layout_events_search.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/_layout_events_search.html
rename to lib/hawat/blueprints/design_bs3/templates/_layout_events_search.html
diff --git a/lib/vial/blueprints/design_bs3/templates/_layout_list.html b/lib/hawat/blueprints/design_bs3/templates/_layout_list.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/_layout_list.html
rename to lib/hawat/blueprints/design_bs3/templates/_layout_list.html
diff --git a/lib/vial/blueprints/design_bs3/templates/_layout_login.html b/lib/hawat/blueprints/design_bs3/templates/_layout_login.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/_layout_login.html
rename to lib/hawat/blueprints/design_bs3/templates/_layout_login.html
diff --git a/lib/vial/blueprints/design_bs3/templates/_layout_registration.html b/lib/hawat/blueprints/design_bs3/templates/_layout_registration.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/_layout_registration.html
rename to lib/hawat/blueprints/design_bs3/templates/_layout_registration.html
diff --git a/lib/vial/blueprints/design_bs3/templates/_layout_search.html b/lib/hawat/blueprints/design_bs3/templates/_layout_search.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/_layout_search.html
rename to lib/hawat/blueprints/design_bs3/templates/_layout_search.html
diff --git a/lib/vial/blueprints/design_bs3/templates/_macros_chart.html b/lib/hawat/blueprints/design_bs3/templates/_macros_chart.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/_macros_chart.html
rename to lib/hawat/blueprints/design_bs3/templates/_macros_chart.html
diff --git a/lib/vial/blueprints/design_bs3/templates/_macros_form.html b/lib/hawat/blueprints/design_bs3/templates/_macros_form.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/_macros_form.html
rename to lib/hawat/blueprints/design_bs3/templates/_macros_form.html
diff --git a/lib/vial/blueprints/design_bs3/templates/_macros_page.html b/lib/hawat/blueprints/design_bs3/templates/_macros_page.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/_macros_page.html
rename to lib/hawat/blueprints/design_bs3/templates/_macros_page.html
diff --git a/lib/vial/blueprints/design_bs3/templates/_macros_site.html b/lib/hawat/blueprints/design_bs3/templates/_macros_site.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/_macros_site.html
rename to lib/hawat/blueprints/design_bs3/templates/_macros_site.html
diff --git a/lib/vial/blueprints/design_bs3/templates/form_delete.html b/lib/hawat/blueprints/design_bs3/templates/form_delete.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/form_delete.html
rename to lib/hawat/blueprints/design_bs3/templates/form_delete.html
diff --git a/lib/vial/blueprints/design_bs3/templates/form_disable.html b/lib/hawat/blueprints/design_bs3/templates/form_disable.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/form_disable.html
rename to lib/hawat/blueprints/design_bs3/templates/form_disable.html
diff --git a/lib/vial/blueprints/design_bs3/templates/form_enable.html b/lib/hawat/blueprints/design_bs3/templates/form_enable.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/form_enable.html
rename to lib/hawat/blueprints/design_bs3/templates/form_enable.html
diff --git a/lib/vial/blueprints/design_bs3/templates/http_error.html b/lib/hawat/blueprints/design_bs3/templates/http_error.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/http_error.html
rename to lib/hawat/blueprints/design_bs3/templates/http_error.html
diff --git a/lib/vial/blueprints/design_bs3/templates/spt_flashmessage.html b/lib/hawat/blueprints/design_bs3/templates/spt_flashmessage.html
similarity index 100%
rename from lib/vial/blueprints/design_bs3/templates/spt_flashmessage.html
rename to lib/hawat/blueprints/design_bs3/templates/spt_flashmessage.html
diff --git a/lib/vial/blueprints/devtools/__init__.py b/lib/hawat/blueprints/devtools/__init__.py
similarity index 84%
rename from lib/vial/blueprints/devtools/__init__.py
rename to lib/hawat/blueprints/devtools/__init__.py
index 1e7616a10..0c162d02b 100644
--- a/lib/vial/blueprints/devtools/__init__.py
+++ b/lib/hawat/blueprints/devtools/__init__.py
@@ -13,10 +13,10 @@ This pluggable module provides various utility and development tools.
 import flask_debugtoolbar
 from flask_babel import lazy_gettext
 
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import SimpleView
-from vial.view.mixin import HTMLMixin
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import SimpleView
+from hawat.view.mixin import HTMLMixin
 
 
 BLUEPRINT_NAME = 'devtools'
@@ -30,7 +30,7 @@ class ConfigView(HTMLMixin, SimpleView):
 
     authentication = True
 
-    authorization = [vial.acl.PERMISSION_DEVELOPER]
+    authorization = [hawat.acl.PERMISSION_DEVELOPER]
 
     @classmethod
     def get_view_name(cls):
@@ -52,7 +52,7 @@ class ConfigView(HTMLMixin, SimpleView):
 #-------------------------------------------------------------------------------
 
 
-class DevtoolsBlueprint(VialBlueprint):
+class DevtoolsBlueprint(HawatBlueprint):
     """Pluggable module - development tools (*devtools*)."""
 
     @classmethod
@@ -75,8 +75,8 @@ class DevtoolsBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/vial/blueprints/devtools/templates/devtools/config.html b/lib/hawat/blueprints/devtools/templates/devtools/config.html
similarity index 100%
rename from lib/vial/blueprints/devtools/templates/devtools/config.html
rename to lib/hawat/blueprints/devtools/templates/devtools/config.html
diff --git a/lib/vial/blueprints/devtools/test/__init__.py b/lib/hawat/blueprints/devtools/test/__init__.py
similarity index 78%
rename from lib/vial/blueprints/devtools/test/__init__.py
rename to lib/hawat/blueprints/devtools/test/__init__.py
index d4ac628a4..c35d8f328 100644
--- a/lib/vial/blueprints/devtools/test/__init__.py
+++ b/lib/hawat/blueprints/devtools/test/__init__.py
@@ -6,7 +6,7 @@
 
 
 """
-Unit tests for :py:mod:`vial.blueprints.devtools`.
+Unit tests for :py:mod:`hawat.blueprints.devtools`.
 """
 
 
@@ -14,16 +14,16 @@ import sys
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
-from vial.test.runner import TestRunnerMixin
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
+from hawat.test.runner import TestRunnerMixin
 
 
 _IS_NOSE = sys.argv[0].endswith('nosetests')
 
 @unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class ConfigTestCase(TestRunnerMixin, VialTestCase):
+class ConfigTestCase(TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``devtools.config`` endpoint.
 
@@ -53,22 +53,22 @@ class ConfigTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_fail()
diff --git a/lib/hawat/blueprints/dnsr/__init__.py b/lib/hawat/blueprints/dnsr/__init__.py
index 058eb4189..d58280ace 100644
--- a/lib/hawat/blueprints/dnsr/__init__.py
+++ b/lib/hawat/blueprints/dnsr/__init__.py
@@ -51,13 +51,13 @@ from flask_babel import lazy_gettext
 import mentat.services.dnsr
 from mentat.const import tr_
 
-import vial.db
+import hawat.db
 import hawat.const
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import RenderableView
-from vial.view.mixin import HTMLMixin, AJAXMixin, SnippetMixin
-from vial.utils import URLParamsBuilder
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import RenderableView
+from hawat.view.mixin import HTMLMixin, AJAXMixin, SnippetMixin
+from hawat.utils import URLParamsBuilder
 import hawat.const
 from hawat.blueprints.dnsr.forms import DnsrSearchForm
 
@@ -163,7 +163,7 @@ class SnippetSearchView(SnippetMixin, AbstractSearchView):  # pylint: disable=lo
 #-------------------------------------------------------------------------------
 
 
-class DnsrBlueprint(VialBlueprint):
+class DnsrBlueprint(HawatBlueprint):
     """Pluggable module - DNS service (*dnsr*)."""
 
     @classmethod
@@ -207,8 +207,8 @@ class DnsrBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/dnsr/test/__init__.py b/lib/hawat/blueprints/dnsr/test/__init__.py
index d04f83435..c84fb4efc 100644
--- a/lib/hawat/blueprints/dnsr/test/__init__.py
+++ b/lib/hawat/blueprints/dnsr/test/__init__.py
@@ -16,13 +16,13 @@ Unit tests for :py:mod:`hawat.blueprints.dnsr`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class SearchTestCase(TestRunnerMixin, VialTestCase):
+class SearchTestCase(TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``dnsr.search`` endpoint.
     """
@@ -51,22 +51,22 @@ class SearchTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail_redirect()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/hawat/blueprints/events/__init__.py b/lib/hawat/blueprints/events/__init__.py
index 2e328c613..9eb61c968 100644
--- a/lib/hawat/blueprints/events/__init__.py
+++ b/lib/hawat/blueprints/events/__init__.py
@@ -32,11 +32,11 @@ from mentat.const import tr_
 import hawat.const
 import hawat.events
 import hawat.const
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import BaseView, SimpleView, BaseSearchView, ItemShowView
-from vial.view.mixin import HTMLMixin, AJAXMixin, SQLAlchemyMixin
-from vial.utils import URLParamsBuilder
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import BaseView, SimpleView, BaseSearchView, ItemShowView
+from hawat.view.mixin import HTMLMixin, AJAXMixin, SQLAlchemyMixin
+from hawat.utils import URLParamsBuilder
 from hawat.base import PsycopgMixin
 from hawat.blueprints.events.forms import SimpleEventSearchForm, EventDashboardForm
 
@@ -65,8 +65,8 @@ def _get_search_form(request_args = None):
 
     # In case no time bounds were set adjust them manually.
     if request_args and not ('dt_from' in request_args or 'dt_to' in request_args or 'st_from' in request_args or 'st_to' in request_args):
-        form.dt_from.process_data(vial.forms.default_dt_with_delta())
-        form.dt_to.process_data(vial.forms.default_dt())
+        form.dt_from.process_data(hawat.forms.default_dt_with_delta())
+        form.dt_to.process_data(hawat.forms.default_dt())
 
     return form
 
@@ -121,7 +121,7 @@ class SearchView(HTMLMixin, AbstractSearchView):  # pylint: disable=locally-disa
 
     @classmethod
     def get_breadcrumbs_menu(cls):
-        breadcrumbs_menu = vial.menu.Menu()
+        breadcrumbs_menu = hawat.menu.Menu()
         breadcrumbs_menu.add_entry(
             'endpoint',
             'home',
@@ -136,7 +136,7 @@ class SearchView(HTMLMixin, AbstractSearchView):  # pylint: disable=locally-disa
 
     @classmethod
     def get_context_action_menu(cls):
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'show',
@@ -198,7 +198,7 @@ class ShowView(HTMLMixin, AbstractShowView):  # pylint: disable=locally-disabled
 
     @classmethod
     def get_action_menu(cls):  # pylint: disable=locally-disabled,unused-argument
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'download',
@@ -416,7 +416,7 @@ class APIMetadataView(AJAXMixin, SimpleView):
 #-------------------------------------------------------------------------------
 
 
-class EventsBlueprint(VialBlueprint):
+class EventsBlueprint(HawatBlueprint):
     """Pluggable module - `IDEA <https://idea.cesnet.cz/en/index>`__ event database (*events*)."""
 
     @classmethod
@@ -560,8 +560,8 @@ class EventsBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/events/forms.py b/lib/hawat/blueprints/events/forms.py
index f26986c38..c30221556 100644
--- a/lib/hawat/blueprints/events/forms.py
+++ b/lib/hawat/blueprints/events/forms.py
@@ -26,8 +26,8 @@ from flask_babel import lazy_gettext
 
 from mentat.datatype.sqldb import GroupModel
 import hawat.const
-import vial.forms
-import vial.db
+import hawat.forms
+import hawat.db
 import hawat.const
 
 
@@ -35,91 +35,91 @@ def get_available_groups():
     """
     Query the database for list of all available groups.
     """
-    return vial.db.db_query(GroupModel).order_by(GroupModel.name).all()
+    return hawat.db.db_query(GroupModel).order_by(GroupModel.name).all()
 
 
-class SimpleEventSearchForm(vial.forms.BaseSearchForm):
+class SimpleEventSearchForm(hawat.forms.BaseSearchForm):
     """
     Class representing simple event search form.
     """
-    dt_from = vial.forms.SmartDateTimeField(
+    dt_from = hawat.forms.SmartDateTimeField(
         lazy_gettext('Detection time from:'),
         validators = [
             wtforms.validators.Optional()
         ],
         description = lazy_gettext('Lower time boundary for event detection time as provided by event detector. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences. Event detectors are usually outside of the control of Mentat system administrators and may sometimes emit events with invalid detection times, for example timestamps in the future.'),
-        default = lambda: vial.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
+        default = lambda: hawat.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
     )
-    dt_to = vial.forms.SmartDateTimeField(
+    dt_to = hawat.forms.SmartDateTimeField(
         lazy_gettext('Detection time to:'),
         validators = [
             wtforms.validators.Optional()
         ],
         description = lazy_gettext('Upper time boundary for event detection time as provided by event detector. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences. Event detectors are usually outside of the control of Mentat system administrators and may sometimes emit events with invalid detection times, for example timestamps in the future.'),
-        default = vial.forms.default_dt
+        default = hawat.forms.default_dt
     )
-    st_from = vial.forms.SmartDateTimeField(
+    st_from = hawat.forms.SmartDateTimeField(
         lazy_gettext('Storage time from:'),
         validators = [
             wtforms.validators.Optional()
         ],
         description = lazy_gettext('Lower time boundary for event storage time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences. Event storage time is provided by Mentat system itself. It is a timestamp of the exact moment the event was stored into the database.')
     )
-    st_to = vial.forms.SmartDateTimeField(
+    st_to = hawat.forms.SmartDateTimeField(
         lazy_gettext('Storage time to:'),
         validators = [
             wtforms.validators.Optional()
         ],
         description = lazy_gettext('Upper time boundary for event storage time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences. Event storage time is provided by Mentat system itself. It is a timestamp of the exact moment the event was stored into the database.')
     )
-    source_addrs = vial.forms.CommaListField(
+    source_addrs = hawat.forms.CommaListField(
         lazy_gettext('Source addresses:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_network_record_list
+            hawat.forms.check_network_record_list
         ],
         widget = wtforms.widgets.TextArea(),
         description = lazy_gettext('Comma separated list of event source IP4/6 addresses, ranges or networks. In this context a source does not necessarily mean a source of the connection, but rather a source of the problem as reported by a detector. Any additional whitespace is ignored and may be used for better readability.')
     )
-    target_addrs = vial.forms.CommaListField(
+    target_addrs = hawat.forms.CommaListField(
         lazy_gettext('Target addresses:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_network_record_list
+            hawat.forms.check_network_record_list
         ],
         widget = wtforms.widgets.TextArea(),
         description = lazy_gettext('Comma separated list of event target IP4/6 addresses, ranges or networks. In this context a target does not necessarily mean a target of the connection, but rather a victim of the problem as reported by a detector. Any additional whitespace is ignored and may be used for better readability.')
     )
-    host_addrs = vial.forms.CommaListField(
+    host_addrs = hawat.forms.CommaListField(
         lazy_gettext('Host addresses:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_network_record_list
+            hawat.forms.check_network_record_list
         ],
         widget = wtforms.widgets.TextArea(),
         description = lazy_gettext('Comma separated list of event source or target IP4/6 addresses, ranges or networks. Any additional whitespace is ignored and may be used for better readability.')
     )
-    source_ports = vial.forms.CommaListField(
+    source_ports = hawat.forms.CommaListField(
         lazy_gettext('Source ports:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_port_list
+            hawat.forms.check_port_list
         ],
         description = lazy_gettext('Comma separated list of source ports as integers. In this context a source does not necessarily mean a source of the connection, but rather a source of the problem as reported by a detector. Any additional whitespace is ignored and may be used for better readability.')
     )
-    target_ports = vial.forms.CommaListField(
+    target_ports = hawat.forms.CommaListField(
         lazy_gettext('Target ports:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_port_list
+            hawat.forms.check_port_list
         ],
         description = lazy_gettext('Comma separated list of target ports as integers. In this context a target does not necessarily mean a target of the connection, but rather a victim of the problem as reported by a detector. Any additional whitespace is ignored and may be used for better readability.')
     )
-    host_ports = vial.forms.CommaListField(
+    host_ports = hawat.forms.CommaListField(
         lazy_gettext('Host ports:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_port_list
+            hawat.forms.check_port_list
         ],
         description = lazy_gettext('Comma separated list of source or target ports as integers. Any additional whitespace is ignored and may be used for better readability.')
     )
@@ -346,21 +346,21 @@ class EventDashboardForm(flask_wtf.FlaskForm):
     """
     Class representing event dashboard search form.
     """
-    dt_from = vial.forms.SmartDateTimeField(
+    dt_from = hawat.forms.SmartDateTimeField(
         lazy_gettext('From:'),
         validators = [
             wtforms.validators.Optional()
         ],
         description = lazy_gettext('Lower time boundary for event detection time as provided by event detector. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences. Event detectors are usually outside of the control of Mentat system administrators and may sometimes emit events with invalid detection times, for example timestamps in the future.'),
-        default = lambda: vial.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
+        default = lambda: hawat.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
     )
-    dt_to = vial.forms.SmartDateTimeField(
+    dt_to = hawat.forms.SmartDateTimeField(
         lazy_gettext('To:'),
         validators = [
             wtforms.validators.Optional()
         ],
         description = lazy_gettext('Upper time boundary for event detection time as provided by event detector. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences. Event detectors are usually outside of the control of Mentat system administrators and may sometimes emit events with invalid detection times, for example timestamps in the future.'),
-        default = vial.forms.default_dt
+        default = hawat.forms.default_dt
     )
     submit = wtforms.SubmitField(
         lazy_gettext('Search')
diff --git a/lib/hawat/blueprints/events/test/__init__.py b/lib/hawat/blueprints/events/test/__init__.py
index 6dca89f13..e34c1e5ed 100644
--- a/lib/hawat/blueprints/events/test/__init__.py
+++ b/lib/hawat/blueprints/events/test/__init__.py
@@ -16,13 +16,13 @@ Unit tests for :py:mod:`hawat.blueprints.events`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class SearchTestCase(TestRunnerMixin, VialTestCase):
+class SearchTestCase(TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``events.search`` endpoint.
     """
@@ -51,28 +51,28 @@ class SearchTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail_redirect()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
 
 
-class DashboardTestCase(TestRunnerMixin, VialTestCase):
+class DashboardTestCase(TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``events.dashboard`` endpoint.
     """
@@ -101,22 +101,22 @@ class DashboardTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail_redirect()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/hawat/blueprints/filters/__init__.py b/lib/hawat/blueprints/filters/__init__.py
index c93362daa..d17fbcdea 100644
--- a/lib/hawat/blueprints/filters/__init__.py
+++ b/lib/hawat/blueprints/filters/__init__.py
@@ -46,10 +46,10 @@ from mentat.datatype.sqldb import FilterModel, GroupModel, ItemChangeLogModel
 from mentat.idea.internal import Idea, IDEAFilterCompiler
 
 import hawat.const
-import vial.db
-from vial.app import VialBlueprint
-from vial.view import RenderableView, ItemListView, ItemShowView, ItemCreateView, ItemCreateForView, ItemUpdateView, ItemDeleteView, ItemEnableView, ItemDisableView
-from vial.view.mixin import HTMLMixin, SQLAlchemyMixin
+import hawat.db
+from hawat.app import HawatBlueprint
+from hawat.view import RenderableView, ItemListView, ItemShowView, ItemCreateView, ItemCreateForView, ItemUpdateView, ItemDeleteView, ItemEnableView, ItemDisableView
+from hawat.view.mixin import HTMLMixin, SQLAlchemyMixin
 import hawat.events
 from hawat.blueprints.filters.forms import BaseFilterForm, AdminFilterForm, PlaygroundFilterForm, FilterSearchForm
 
@@ -134,7 +134,7 @@ class ListView(HTMLMixin, SQLAlchemyMixin, ItemListView):
 
     authentication = True
 
-    authorization = [vial.acl.PERMISSION_POWER]
+    authorization = [hawat.acl.PERMISSION_POWER]
 
     @classmethod
     def get_view_title(cls, **kwargs):
@@ -148,7 +148,7 @@ class ListView(HTMLMixin, SQLAlchemyMixin, ItemListView):
 
     @classmethod
     def get_action_menu(cls):
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'create',
@@ -165,7 +165,7 @@ class ListView(HTMLMixin, SQLAlchemyMixin, ItemListView):
 
     @classmethod
     def get_context_action_menu(cls):
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'show',
@@ -281,14 +281,14 @@ class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView):
     @classmethod
     def authorize_item_action(cls, **kwargs):
         permission_mm = flask_principal.Permission(
-            vial.acl.MembershipNeed(kwargs['item'].group.id),
-            vial.acl.ManagementNeed(kwargs['item'].group.id)
+            hawat.acl.MembershipNeed(kwargs['item'].group.id),
+            hawat.acl.ManagementNeed(kwargs['item'].group.id)
         )
-        return vial.acl.PERMISSION_POWER.can() or permission_mm.can()
+        return hawat.acl.PERMISSION_POWER.can() or permission_mm.can()
 
     @classmethod
     def get_action_menu(cls):
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'update',
@@ -369,7 +369,7 @@ class CreateView(HTMLMixin, SQLAlchemyMixin, ItemCreateView):  # pylint: disable
 
     @classmethod
     def authorize_item_action(cls, **kwargs):
-        return vial.acl.PERMISSION_POWER.can()
+        return hawat.acl.PERMISSION_POWER.can()
 
     @staticmethod
     def get_message_success(**kwargs):
@@ -467,9 +467,9 @@ class CreateForView(HTMLMixin, SQLAlchemyMixin, ItemCreateForView):  # pylint: d
     @classmethod
     def authorize_item_action(cls, **kwargs):
         permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].id)
+            hawat.acl.ManagementNeed(kwargs['item'].id)
         )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
 
     @staticmethod
     def get_message_success(**kwargs):
@@ -550,9 +550,9 @@ class UpdateView(HTMLMixin, SQLAlchemyMixin, ItemUpdateView):  # pylint: disable
     @classmethod
     def authorize_item_action(cls, **kwargs):
         permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].group.id)
+            hawat.acl.ManagementNeed(kwargs['item'].group.id)
         )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
 
     @staticmethod
     def get_message_success(**kwargs):
@@ -635,9 +635,9 @@ class EnableView(HTMLMixin, SQLAlchemyMixin, ItemEnableView):  # pylint: disable
     @classmethod
     def authorize_item_action(cls, **kwargs):
         permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].group.id)
+            hawat.acl.ManagementNeed(kwargs['item'].group.id)
         )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
 
     @staticmethod
     def get_message_success(**kwargs):
@@ -692,9 +692,9 @@ class DisableView(HTMLMixin, SQLAlchemyMixin, ItemDisableView):  # pylint: disab
     @classmethod
     def authorize_item_action(cls, **kwargs):
         permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].group.id)
+            hawat.acl.ManagementNeed(kwargs['item'].group.id)
         )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
 
     @staticmethod
     def get_message_success(**kwargs):
@@ -747,9 +747,9 @@ class DeleteView(HTMLMixin, SQLAlchemyMixin, ItemDeleteView):  # pylint: disable
     @classmethod
     def authorize_item_action(cls, **kwargs):
         permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].group.id)
+            hawat.acl.ManagementNeed(kwargs['item'].group.id)
         )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
 
     @staticmethod
     def get_message_success(**kwargs):
@@ -806,7 +806,7 @@ class PlaygroundView(HTMLMixin, RenderableView):
 
     @classmethod
     def get_breadcrumbs_menu(cls):
-        breadcrumbs_menu = vial.menu.Menu()
+        breadcrumbs_menu = hawat.menu.Menu()
         breadcrumbs_menu.add_entry(
             'endpoint',
             'home',
@@ -879,7 +879,7 @@ class PlaygroundView(HTMLMixin, RenderableView):
 #-------------------------------------------------------------------------------
 
 
-class FiltersBlueprint(VialBlueprint):
+class FiltersBlueprint(HawatBlueprint):
     """Pluggable module - reporting filter management (*filters*)."""
 
     @classmethod
@@ -907,8 +907,8 @@ class FiltersBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/filters/forms.py b/lib/hawat/blueprints/filters/forms.py
index 8fdcbea92..358a13588 100644
--- a/lib/hawat/blueprints/filters/forms.py
+++ b/lib/hawat/blueprints/filters/forms.py
@@ -31,8 +31,8 @@ from flask_babel import gettext, lazy_gettext
 #
 # Custom modules.
 #
-import vial.db
-import vial.forms
+import hawat.db
+import hawat.forms
 from mentat.datatype.sqldb import GroupModel
 from mentat.const import REPORTING_FILTER_BASIC, REPORTING_FILTER_ADVANCED
 from mentat.idea.internal import Idea
@@ -42,7 +42,7 @@ def get_available_groups():
     """
     Query the database for list of all available groups.
     """
-    return vial.db.db_query(GroupModel).order_by(GroupModel.name).all()
+    return hawat.db.db_query(GroupModel).order_by(GroupModel.name).all()
 
 
 def check_filter(form, field):  # pylint: disable=locally-disabled,unused-argument
@@ -79,7 +79,7 @@ def check_event(form, field):  # pylint: disable=locally-disabled,unused-argumen
 #-------------------------------------------------------------------------------
 
 
-class BaseFilterForm(vial.forms.BaseItemForm):
+class BaseFilterForm(hawat.forms.BaseItemForm):
     """
     Class representing base reporting filter form.
     """
@@ -129,21 +129,21 @@ class BaseFilterForm(vial.forms.BaseItemForm):
         choices = [('', lazy_gettext('<< no preference >>'))],
         filters = [lambda x: x or []]
     )
-    ips = vial.forms.CommaListField(
+    ips = hawat.forms.CommaListField(
         lazy_gettext('Source IPs:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_network_record_list
+            hawat.forms.check_network_record_list
         ],
         widget = wtforms.widgets.TextArea()
     )
-    valid_from = vial.forms.SmartDateTimeField(
+    valid_from = hawat.forms.SmartDateTimeField(
         lazy_gettext('Valid from:'),
         validators = [
             wtforms.validators.Optional()
         ]
     )
-    valid_to = vial.forms.SmartDateTimeField(
+    valid_to = hawat.forms.SmartDateTimeField(
         lazy_gettext('Valid to:'),
         validators = [
             wtforms.validators.Optional()
@@ -158,8 +158,8 @@ class BaseFilterForm(vial.forms.BaseItemForm):
             (True,  lazy_gettext('Enabled')),
             (False, lazy_gettext('Disabled'))
         ],
-        filters = [vial.forms.str_to_bool],
-        coerce = vial.forms.str_to_bool
+        filters = [hawat.forms.str_to_bool],
+        coerce = hawat.forms.str_to_bool
     )
     submit = wtforms.SubmitField(
         lazy_gettext('Submit')
@@ -210,7 +210,7 @@ class PlaygroundFilterForm(flask_wtf.FlaskForm):
         lazy_gettext('Check')
     )
 
-class FilterSearchForm(vial.forms.BaseSearchForm):
+class FilterSearchForm(hawat.forms.BaseSearchForm):
     """
     Class representing simple user search form.
     """
@@ -222,14 +222,14 @@ class FilterSearchForm(vial.forms.BaseSearchForm):
         ],
         description = lazy_gettext('Filter`s name, content or description. Search is performed even in the middle of the strings.')
     )
-    dt_from = vial.forms.SmartDateTimeField(
+    dt_from = hawat.forms.SmartDateTimeField(
         lazy_gettext('Creation time from:'),
         validators = [
             wtforms.validators.Optional()
         ],
         description = lazy_gettext('Lower time boundary for item creation time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences.')
     )
-    dt_to = vial.forms.SmartDateTimeField(
+    dt_to = hawat.forms.SmartDateTimeField(
         lazy_gettext('Creation time to:'),
         validators = [
             wtforms.validators.Optional()
diff --git a/lib/hawat/blueprints/filters/test/__init__.py b/lib/hawat/blueprints/filters/test/__init__.py
index 0c5de8b1c..5ac1975d1 100644
--- a/lib/hawat/blueprints/filters/test/__init__.py
+++ b/lib/hawat/blueprints/filters/test/__init__.py
@@ -18,10 +18,10 @@ import unittest
 from mentat.datatype.sqldb import FilterModel
 
 import hawat.const
-import vial.test
-import vial.test.fixtures
-import vial.db
-from vial.test import VialTestCase, ItemCreateVialTestCase
+import hawat.test
+import hawat.test.fixtures
+import hawat.db
+from hawat.test import HawatTestCase, ItemCreateHawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
@@ -39,20 +39,20 @@ class FilterTestMixin:
         Get given filter.
         """
         if not with_app_context:
-            return vial.db.db_session().query(FilterModel).filter(FilterModel.name == filter_name).one_or_none()
+            return hawat.db.db_session().query(FilterModel).filter(FilterModel.name == filter_name).one_or_none()
         with self.app.app_context():
-            return vial.db.db_session().query(FilterModel).filter(FilterModel.name == filter_name).one_or_none()
+            return hawat.db.db_session().query(FilterModel).filter(FilterModel.name == filter_name).one_or_none()
 
     def filter_save(self, filter_object, with_app_context = False):
         """
         Update given filter.
         """
         if not with_app_context:
-            vial.db.db_session().add(filter_object)
-            vial.db.db_session().commit()
+            hawat.db.db_session().add(filter_object)
+            hawat.db.db_session().commit()
         with self.app.app_context():
-            vial.db.db_session().add(filter_object)
-            vial.db.db_session().commit()
+            hawat.db.db_session().add(filter_object)
+            hawat.db.db_session().commit()
 
     def filter_id(self, filter_type, with_app_context = False):
         """
@@ -66,7 +66,7 @@ class FilterTestMixin:
             return fobj.id
 
 
-class FiltersListTestCase(TestRunnerMixin, VialTestCase):
+class FiltersListTestCase(TestRunnerMixin, HawatTestCase):
     """Class for testing ``filters.list`` endpoint."""
 
     def _attempt_fail(self):
@@ -85,28 +85,28 @@ class FiltersListTestCase(TestRunnerMixin, VialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user ``user``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
 
 
-class FiltersShowTestCase(FilterTestMixin, TestRunnerMixin, VialTestCase):
+class FiltersShowTestCase(FilterTestMixin, TestRunnerMixin, HawatTestCase):
     """Base class for testing ``filters.show`` endpoint."""
 
     def _attempt_fail(self, fname):
@@ -127,48 +127,48 @@ class FiltersShowTestCase(FilterTestMixin, TestRunnerMixin, VialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """
         Test access as user 'user'.
 
         Only power user is able to view all available filters.
         """
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """
         Test access as user 'developer'.
 
         Only power user is able to view all available filters.
         """
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """
         Test access as user 'maintainer'.
 
         Only power user is able to view all available filters.
         """
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """
         Test access as user 'admin'.
 
         Only power user is able to view all available filters.
         """
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
 
-class FiltersCreateTestCase(FilterTestMixin, TestRunnerMixin, ItemCreateVialTestCase):
+class FiltersCreateTestCase(FilterTestMixin, TestRunnerMixin, ItemCreateHawatTestCase):
     """Class for testing ``filters.create`` endpoint."""
 
     filter_data_fixture = [
@@ -197,28 +197,28 @@ class FiltersCreateTestCase(FilterTestMixin, TestRunnerMixin, ItemCreateVialTest
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
         self._attempt_succeed()
 
 
-class FiltersCreateForTestCase(FilterTestMixin, TestRunnerMixin, ItemCreateVialTestCase):
+class FiltersCreateForTestCase(FilterTestMixin, TestRunnerMixin, ItemCreateHawatTestCase):
     """Class for testing ``filters.createfor`` endpoint."""
 
     filter_data_fixture = [
@@ -248,32 +248,32 @@ class FiltersCreateForTestCase(FilterTestMixin, TestRunnerMixin, ItemCreateVialT
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_B)
 
 
-class FiltersUpdateTestCase(FilterTestMixin, TestRunnerMixin, VialTestCase):
+class FiltersUpdateTestCase(FilterTestMixin, TestRunnerMixin, HawatTestCase):
     """Class for testing ``filters.update`` endpoint."""
 
     def _attempt_fail(self, fname):
@@ -293,32 +293,32 @@ class FiltersUpdateTestCase(FilterTestMixin, TestRunnerMixin, VialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
-        self._attempt_fail(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_fail(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_04_as_developer(self):
         """Test access as user 'developer'."""
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_05_as_maintainer(self):
         """Test access as user 'maintainer'."""
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_06_as_admin(self):
         """Test access as user 'admin'."""
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
 
-class FiltersEnableDisableTestCase(FilterTestMixin, TestRunnerMixin, VialTestCase):
+class FiltersEnableDisableTestCase(FilterTestMixin, TestRunnerMixin, HawatTestCase):
     """Class for testing ``filters.enable`` and ``filters.disable`` endpoint."""
 
     def _attempt_fail(self, fname):
@@ -369,32 +369,32 @@ class FiltersEnableDisableTestCase(FilterTestMixin, TestRunnerMixin, VialTestCas
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
-        self._attempt_fail(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_fail(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
 
-class FiltersDeleteTestCase(FilterTestMixin, TestRunnerMixin, VialTestCase):
+class FiltersDeleteTestCase(FilterTestMixin, TestRunnerMixin, HawatTestCase):
     """Class for testing ``filters.delete`` endpoint."""
 
     def _attempt_fail(self, fname):
@@ -424,29 +424,29 @@ class FiltersDeleteTestCase(FilterTestMixin, TestRunnerMixin, VialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
-        self._attempt_fail(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_fail(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._fname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._fname(hawat.test.fixtures.DEMO_GROUP_B))
 
 
 #-------------------------------------------------------------------------------
diff --git a/lib/hawat/blueprints/geoip/__init__.py b/lib/hawat/blueprints/geoip/__init__.py
index 5e9b8d148..43f52f376 100644
--- a/lib/hawat/blueprints/geoip/__init__.py
+++ b/lib/hawat/blueprints/geoip/__init__.py
@@ -54,13 +54,13 @@ from flask_babel import lazy_gettext
 import mentat.services.geoip
 from mentat.const import tr_
 
-import vial.db
+import hawat.db
 import hawat.const
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import RenderableView
-from vial.view.mixin import HTMLMixin, AJAXMixin, SnippetMixin
-from vial.utils import URLParamsBuilder
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import RenderableView
+from hawat.view.mixin import HTMLMixin, AJAXMixin, SnippetMixin
+from hawat.utils import URLParamsBuilder
 import hawat.const
 from hawat.blueprints.geoip.forms import GeoipSearchForm
 
@@ -169,7 +169,7 @@ class SnippetSearchView(SnippetMixin, AbstractSearchView):  # pylint: disable=lo
 #-------------------------------------------------------------------------------
 
 
-class GeoipBlueprint(VialBlueprint):
+class GeoipBlueprint(HawatBlueprint):
     """Pluggable module - IP geolocation service (*geoip*)."""
 
     @classmethod
@@ -212,8 +212,8 @@ class GeoipBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/geoip/forms.py b/lib/hawat/blueprints/geoip/forms.py
index 1ab72bbf8..ef5984718 100644
--- a/lib/hawat/blueprints/geoip/forms.py
+++ b/lib/hawat/blueprints/geoip/forms.py
@@ -21,7 +21,7 @@ import wtforms
 import flask_wtf
 from flask_babel import lazy_gettext
 
-import vial.forms
+import hawat.forms
 
 
 class GeoipSearchForm(flask_wtf.FlaskForm):
@@ -32,7 +32,7 @@ class GeoipSearchForm(flask_wtf.FlaskForm):
         lazy_gettext('Search GeoIP:'),
         validators = [
             wtforms.validators.DataRequired(),
-            vial.forms.check_ip_record
+            hawat.forms.check_ip_record
         ]
     )
     submit = wtforms.SubmitField(
diff --git a/lib/hawat/blueprints/geoip/test/__init__.py b/lib/hawat/blueprints/geoip/test/__init__.py
index 4b2c8970c..75d915be9 100644
--- a/lib/hawat/blueprints/geoip/test/__init__.py
+++ b/lib/hawat/blueprints/geoip/test/__init__.py
@@ -16,13 +16,13 @@ Unit tests for :py:mod:`hawat.blueprints.geoip`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class SearchTestCase(TestRunnerMixin, VialTestCase):
+class SearchTestCase(TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``geoip.search`` endpoint.
     """
@@ -51,22 +51,22 @@ class SearchTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail_redirect()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/hawat/blueprints/groups/__init__.py b/lib/hawat/blueprints/groups/__init__.py
index 008346575..7532bb5f4 100644
--- a/lib/hawat/blueprints/groups/__init__.py
+++ b/lib/hawat/blueprints/groups/__init__.py
@@ -29,30 +29,232 @@ __author__ = "Jan Mach <jan.mach@cesnet.cz>"
 __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
 
 
+import flask
 import flask_login
+import flask_principal
 from flask_babel import gettext, lazy_gettext
+
 from sqlalchemy import and_, or_
 
 from mentat.datatype.sqldb import SettingsReportingModel, FilterModel, NetworkModel, ItemChangeLogModel
 from mentat.const import tr_
 
-import vial.acl
-import vial.menu
-from vial.utils import URLParamsBuilder
-import vial.blueprints.groups
-from vial.blueprints.groups import BLUEPRINT_NAME, ListView, EnableView, DisableView, DeleteView, AddMemberView, RejectMemberView, RemoveMemberView, AddManagerView, RemoveManagerView
-import hawat.const
-from hawat.blueprints.groups.forms import AdminCreateGroupForm, AdminUpdateGroupForm,\
-    UpdateGroupForm
+import hawat.acl
+import hawat.menu
+from hawat.utils import URLParamsBuilder
+from hawat.app import HawatBlueprint
+from hawat.view import ItemListView, ItemShowView, ItemCreateView, ItemUpdateView, ItemDeleteView, ItemEnableView, ItemDisableView, ItemObjectRelationView
+from hawat.view.mixin import HTMLMixin, SQLAlchemyMixin
+from hawat.blueprints.groups.forms import AdminCreateGroupForm, AdminUpdateGroupForm, UpdateGroupForm, GroupSearchForm
+
+
+BLUEPRINT_NAME = 'groups'
+"""Name of the blueprint as module global constant."""
 
 
-class ShowView(vial.blueprints.groups.ShowView):
+class ListView(HTMLMixin, SQLAlchemyMixin, ItemListView):
+    """
+    General group listing.
+    """
+
+    methods = ['GET']
+
+    authentication = True
+
+    authorization = [hawat.acl.PERMISSION_POWER]
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return lazy_gettext('Group management')
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @classmethod
+    def get_action_menu(cls):
+        action_menu = hawat.menu.Menu()
+        action_menu.add_entry(
+            'endpoint',
+            'create',
+            endpoint = 'groups.create',
+            resptitle = True
+        )
+        return action_menu
+
+    @classmethod
+    def get_context_action_menu(cls):
+        action_menu = hawat.menu.Menu()
+        action_menu.add_entry(
+            'endpoint',
+            'show',
+            endpoint = 'groups.show',
+            hidetitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'update',
+            endpoint = 'groups.update',
+            hidetitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'disable',
+            endpoint = 'groups.disable',
+            hidetitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'enable',
+            endpoint = 'groups.enable',
+            hidetitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'delete',
+            endpoint = 'groups.delete',
+            hidetitle = True
+        )
+        return action_menu
+
+    @staticmethod
+    def get_search_form(request_args):
+        """
+        Must return instance of :py:mod:`flask_wtf.FlaskForm` appropriate for
+        searching given type of items.
+        """
+        return GroupSearchForm(
+            request_args,
+            meta = {'csrf': False}
+        )
+
+    @staticmethod
+    def build_query(query, model, form_args):
+        # Adjust query based on text search string.
+        if 'search' in form_args and form_args['search']:
+            query = query\
+                .filter(
+                    or_(
+                        model.name.like('%{}%'.format(form_args['search'])),
+                        model.description.like('%{}%'.format(form_args['search'])),
+                    )
+                )
+        # Adjust query based on lower time boudary selection.
+        if 'dt_from' in form_args and form_args['dt_from']:
+            query = query.filter(model.createtime >= form_args['dt_from'])
+        # Adjust query based on upper time boudary selection.
+        if 'dt_to' in form_args and form_args['dt_to']:
+            query = query.filter(model.createtime <= form_args['dt_to'])
+        # Adjust query based on user state selection.
+        if 'state' in form_args and form_args['state']:
+            if form_args['state'] == 'enabled':
+                query = query.filter(model.enabled == True)
+            elif form_args['state'] == 'disabled':
+                query = query.filter(model.enabled == False)
+        # Adjust query based on record source selection.
+        if 'source' in form_args and form_args['source']:
+            query = query\
+                .filter(model.source == form_args['source'])
+        # Adjust query based on user membership selection.
+        if 'member' in form_args and form_args['member']:
+            query = query\
+                .join(model.members)\
+                .filter(model.members.any(id = form_args['member'].id))
+        # Adjust query based on user membership selection.
+        if 'manager' in form_args and form_args['manager']:
+            query = query\
+                .join(model.managers)\
+                .filter(model.managers.any(id = form_args['managers'].id))
+        if 'sortby' in form_args and form_args['sortby']:
+            sortmap = {
+                'createtime.desc': lambda x, y: x.order_by(y.createtime.desc()),
+                'createtime.asc': lambda x, y: x.order_by(y.createtime.asc()),
+                'name.desc': lambda x, y: x.order_by(y.name.desc()),
+                'name.asc': lambda x, y: x.order_by(y.name.asc())
+            }
+            query = sortmap[form_args['sortby']](query, model)
+        return query
+
+class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView):
     """
     Detailed group view.
     """
 
+    methods = ['GET']
+
+    authentication = True
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        if isinstance(kwargs['item'], cls.get_model(hawat.const.MODEL_GROUP)):
+            return lazy_gettext(
+                'View details of group &quot;%(item)s&quot;',
+                item = flask.escape(str(kwargs['item']))
+            )
+        return lazy_gettext(
+            'View details of group &quot;%(item)s&quot;',
+            item = flask.escape(str(kwargs['item'].group))
+        )
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return lazy_gettext('Show group details')
+
+    @classmethod
+    def get_view_url(cls, **kwargs):
+        if isinstance(kwargs['item'], cls.get_model(hawat.const.MODEL_GROUP)):
+            return flask.url_for(
+                cls.get_view_endpoint(),
+                item_id = kwargs['item'].get_id()
+            )
+        return flask.url_for(
+            cls.get_view_endpoint(),
+            item_id = kwargs['item'].group.get_id()
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        permission_mm = flask_principal.Permission(
+            hawat.acl.MembershipNeed(kwargs['item'].id),
+            hawat.acl.ManagementNeed(kwargs['item'].id)
+        )
+        return hawat.acl.PERMISSION_POWER.can() or permission_mm.can()
+
+    @classmethod
+    def get_action_menu(cls):
+        action_menu = hawat.menu.Menu()
+        action_menu.add_entry(
+            'endpoint',
+            'update',
+            endpoint = 'groups.update',
+            resptitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'disable',
+            endpoint = 'groups.disable',
+            resptitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'enable',
+            endpoint = 'groups.enable',
+            resptitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'delete',
+            endpoint = 'groups.delete',
+            resptitle = True
+        )
+        return action_menu
+
     def do_before_response(self, **kwargs):  # pylint: disable=locally-disabled,no-self-use,unused-argument
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'show',
@@ -107,7 +309,7 @@ class ShowView(vial.blueprints.groups.ShowView):
         )
         self.response_context.update(context_action_menu_users = action_menu)
 
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'show',
@@ -116,7 +318,7 @@ class ShowView(vial.blueprints.groups.ShowView):
         )
         self.response_context.update(context_action_menu_networks = action_menu)
 
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'show',
@@ -188,11 +390,48 @@ class ShowByNameView(ShowView):  # pylint: disable=locally-disabled,too-many-anc
         return self.dbmodel.name
 
 
-class CreateView(vial.blueprints.groups.CreateView):  # pylint: disable=locally-disabled,too-many-ancestors
+class CreateView(HTMLMixin, SQLAlchemyMixin, ItemCreateView):  # pylint: disable=locally-disabled,too-many-ancestors
     """
     View for creating new groups.
     """
 
+    methods = ['GET','POST']
+
+    authentication = True
+
+    authorization = [hawat.acl.PERMISSION_POWER]
+
+    @classmethod
+    def get_menu_title(cls, **kwargs):
+        return lazy_gettext('Create group')
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return lazy_gettext('Create new group')
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'Group <strong>%(item_id)s</strong> was successfully created.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext('Unable to create new group.')
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext('Canceled creating new group.')
+
     @staticmethod
     def get_item_form(item):
         return AdminCreateGroupForm()
@@ -202,11 +441,66 @@ class CreateView(vial.blueprints.groups.CreateView):  # pylint: disable=locally-
         SettingsReportingModel(group = item)
 
 
-class UpdateView(vial.blueprints.groups.UpdateView):  # pylint: disable=locally-disabled,too-many-ancestors
+class UpdateView(HTMLMixin, SQLAlchemyMixin, ItemUpdateView):  # pylint: disable=locally-disabled,too-many-ancestors
     """
     View for updating existing groups.
     """
 
+    methods = ['GET','POST']
+
+    authentication = True
+
+    @classmethod
+    def get_menu_title(cls, **kwargs):
+        return lazy_gettext('Update')
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Update details of group &quot;%(item)s&quot;',
+            item = flask.escape(str(kwargs['item']))
+        )
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return lazy_gettext('Update group details')
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        permission_m = flask_principal.Permission(
+            hawat.acl.ManagementNeed(kwargs['item'].id)
+        )
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'Group <strong>%(item_id)s</strong> was successfully updated.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to update group <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled updating group <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
     @staticmethod
     def get_item_form(item):
         admin = flask_login.current_user.has_role('admin')
@@ -217,12 +511,594 @@ class UpdateView(vial.blueprints.groups.UpdateView):  # pylint: disable=locally-
         return form
 
 
+class AddMemberView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for adding group members.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    @classmethod
+    def get_view_name(cls):
+        return 'addmember'
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return gettext('Add group member')
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-add-member'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Add user &quot;%(user_id)s&quot; to group &quot;%(group_id)s&quot;',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @property
+    def dbmodel_other(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        permission_m = flask_principal.Permission(
+            hawat.acl.ManagementNeed(kwargs['item'].id)
+        )
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
+
+    @classmethod
+    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        # Reject item change in case given item is already enabled.
+        if kwargs['other'] in kwargs['item'].members:
+            return False
+        return True
+
+    @classmethod
+    def change_item(cls, **kwargs):
+        kwargs['item'].members.append(kwargs['other'])
+        try:
+            kwargs['item'].members_wanted.remove(kwargs['other'])
+        except ValueError:
+            pass
+        if kwargs['other'].is_state_disabled():
+            kwargs['other'].set_state_enabled()
+            flask.current_app.send_infomail(
+                'users.enable',
+                account = kwargs['other']
+            )
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User <strong>%(user_id)s</strong> was successfully added as a member to group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to add user <strong>%(user_id)s</strong> as a member to group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled adding user <strong>%(user_id)s</strong> as a member to group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+
+class RejectMemberView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for rejecting group membership reuests.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    @classmethod
+    def get_view_name(cls):
+        return 'rejectmember'
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return gettext('Reject group member')
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-rej-member'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Reject user`s &quot;%(user_id)s&quot; membership request for group &quot;%(group_id)s&quot;',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @property
+    def dbmodel_other(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        permission_m = flask_principal.Permission(
+            hawat.acl.ManagementNeed(kwargs['item'].id)
+        )
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
+
+    @classmethod
+    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        # Reject item change in case given item is already enabled.
+        if kwargs['other'] not in kwargs['item'].members_wanted:
+            return False
+        return True
+
+    @classmethod
+    def change_item(cls, **kwargs):
+        kwargs['item'].members_wanted.remove(kwargs['other'])
+
+    #---------------------------------------------------------------------------
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User`s <strong>%(user_id)s</strong> membership request for group <strong>%(group_id)s</strong> was successfully rejected.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to reject user`s <strong>%(user_id)s</strong> membership request for group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled rejecting user`s <strong>%(user_id)s</strong> membership request for group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+
+class RemoveMemberView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for removing group members.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    @classmethod
+    def get_view_name(cls):
+        return 'removemember'
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return gettext('Remove group member')
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-rem-member'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Remove user &quot;%(user_id)s&quot; from group &quot;%(group_id)s&quot;',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @property
+    def dbmodel_other(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        permission_m = flask_principal.Permission(
+            hawat.acl.ManagementNeed(kwargs['item'].id)
+        )
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
+
+    @classmethod
+    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        # Reject item change in case given item is already enabled.
+        if kwargs['other'] not in kwargs['item'].members:
+            return False
+        return True
+
+    @classmethod
+    def change_item(cls, **kwargs):
+        kwargs['item'].members.remove(kwargs['other'])
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User <strong>%(user_id)s</strong> was successfully removed as a member from group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to remove user <strong>%(user_id)s</strong> as a member from group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled removing user <strong>%(user_id)s</strong> as a member from group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+
+class AddManagerView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for adding group managers.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    @classmethod
+    def get_view_name(cls):
+        return 'addmanager'
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return gettext('Add group manager')
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-add-manager'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Add user &quot;%(user_id)s&quot; to group &quot;%(group_id)s&quot; as manager',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @property
+    def dbmodel_other(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        permission_m = flask_principal.Permission(
+            hawat.acl.ManagementNeed(kwargs['item'].id)
+        )
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
+
+    @classmethod
+    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        # Reject item change in case given item is already manager.
+        if kwargs['other'] in kwargs['item'].managers:
+            return False
+        return True
+
+    @classmethod
+    def change_item(cls, **kwargs):
+        kwargs['item'].managers.append(kwargs['other'])
+        if kwargs['other'].is_state_disabled():
+            kwargs['other'].set_state_enabled()
+            flask.current_app.send_infomail(
+                'users.enable',
+                account = kwargs['other']
+            )
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User <strong>%(user_id)s</strong> was successfully added as a manager to group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to add user <strong>%(user_id)s</strong> as a manager to group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled adding user <strong>%(user_id)s</strong> as a manager to group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+
+class RemoveManagerView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for removing group managers.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    @classmethod
+    def get_view_name(cls):
+        return 'removemanager'
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return gettext('Remove group manager')
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-rem-manager'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Remove user &quot;%(user_id)s&quot; from group &quot;%(group_id)s&quot; as manager',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @property
+    def dbmodel_other(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        permission_m = flask_principal.Permission(
+            hawat.acl.ManagementNeed(kwargs['item'].id)
+        )
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
+
+    @classmethod
+    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        # Reject item change in case given item is not already manager.
+        if kwargs['other'] not in kwargs['item'].managers:
+            return False
+        return True
+
+    @classmethod
+    def change_item(cls, **kwargs):
+        try:
+            kwargs['item'].managers.remove(kwargs['other'])
+        except ValueError:
+            pass
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User <strong>%(user_id)s</strong> was successfully removed as a manager from group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to remove user <strong>%(user_id)s</strong> as a manager from group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled removing user <strong>%(user_id)s</strong> as a manager from group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['other'])),
+            group_id = flask.escape(str(kwargs['item']))
+        )
+
+
+class EnableView(HTMLMixin, SQLAlchemyMixin, ItemEnableView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for enabling existing groups.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    authorization = [hawat.acl.PERMISSION_POWER]
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Enable group &quot;%(item)s&quot;',
+            item = flask.escape(str(kwargs['item']))
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'Group <strong>%(item_id)s</strong> was successfully enabled.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to enable group <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled enabling group <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+
+class DisableView(HTMLMixin, SQLAlchemyMixin, ItemDisableView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for disabling groups.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    authorization = [hawat.acl.PERMISSION_POWER]
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Disable group &quot;%(item)s&quot;',
+            item = flask.escape(str(kwargs['item']))
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'Group <strong>%(item_id)s</strong> was successfully disabled.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to disable group <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled disabling group <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+
+class DeleteView(HTMLMixin, SQLAlchemyMixin, ItemDeleteView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for deleting existing groups.
+    """
+
+    methods = ['GET','POST']
+
+    authentication = True
+
+    authorization = [hawat.acl.PERMISSION_ADMIN]
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Delete group &quot;%(item)s&quot;',
+            item = flask.escape(str(kwargs['item']))
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'Group <strong>%(item_id)s</strong> was successfully and permanently deleted.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to delete group <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled deleting group <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+
 #-------------------------------------------------------------------------------
 
 
-class GroupsBlueprint(vial.blueprints.groups.GroupsBlueprint):
+class GroupsBlueprint(HawatBlueprint):
     """Pluggable module - user groups (*groups*)."""
 
+    @classmethod
+    def get_module_title(cls):
+        return lazy_gettext('Group management')
+
     def register_app(self, app):
 
         def _fetch_my_groups():
@@ -246,7 +1122,7 @@ class GroupsBlueprint(vial.blueprints.groups.GroupsBlueprint):
             icon = 'module-groups',
             align_right = True,
             entry_fetcher = _fetch_my_groups,
-            entry_builder = lambda x, y: vial.menu.EndpointEntry(x, endpoint = 'groups.show', params = {'item': y}, title = x, icon = 'module-groups')
+            entry_builder = lambda x, y: hawat.menu.EndpointEntry(x, endpoint = 'groups.show', params = {'item': y}, title = x, icon = 'module-groups')
         )
 
         # Register context actions provided by this module.
@@ -263,8 +1139,8 @@ class GroupsBlueprint(vial.blueprints.groups.GroupsBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/groups/forms.py b/lib/hawat/blueprints/groups/forms.py
index 865a31da2..cc7d9e3d0 100644
--- a/lib/hawat/blueprints/groups/forms.py
+++ b/lib/hawat/blueprints/groups/forms.py
@@ -29,8 +29,9 @@ from flask_babel import gettext, lazy_gettext
 #
 # Custom modules.
 #
-import vial.db
-import vial.forms
+import hawat.db
+import hawat.forms
+from hawat.forms import get_available_users, get_available_group_sources
 from mentat.datatype.sqldb import GroupModel, UserModel
 
 
@@ -39,7 +40,7 @@ def check_name_existence(form, field):  # pylint: disable=locally-disabled,unuse
     Callback for validating user logins during account create action.
     """
     try:
-        vial.db.db_get().session.query(GroupModel).\
+        hawat.db.db_get().session.query(GroupModel).\
             filter(GroupModel.name == field.data).\
             one()
     except sqlalchemy.orm.exc.NoResultFound:
@@ -53,7 +54,7 @@ def check_name_uniqueness(form, field):
     """
     Callback for validating user logins during account update action.
     """
-    item = vial.db.db_get().session.query(GroupModel).\
+    item = hawat.db.db_get().session.query(GroupModel).\
         filter(GroupModel.name == field.data).\
         filter(GroupModel.id != form.db_item_id).\
         all()
@@ -74,7 +75,7 @@ def get_available_users():
     """
     Query the database for list of all available user accounts.
     """
-    return vial.db.db_query(UserModel).order_by(UserModel.fullname).all()
+    return hawat.db.db_query(UserModel).order_by(UserModel.fullname).all()
 
 
 def format_select_option_label_user(item):
@@ -88,10 +89,9 @@ def get_available_groups():
     """
     Query the database for list of all available groups.
     """
-    return vial.db.db_query(GroupModel).order_by(GroupModel.name).all()
+    return hawat.db.db_query(GroupModel).order_by(GroupModel.name).all()
 
-
-class BaseGroupForm(vial.forms.BaseItemForm):
+class BaseGroupForm(hawat.forms.BaseItemForm):
     """
     Class representing base group form.
     """
@@ -112,7 +112,7 @@ class BaseGroupForm(vial.forms.BaseItemForm):
     )
     members = QuerySelectMultipleField(
         lazy_gettext('Members:'),
-        query_factory = get_available_users,
+        query_factory = hawat.forms.get_available_users,
         get_label = format_select_option_label_user,
         blank_text = lazy_gettext('<< no selection >>'),
         description = lazy_gettext('List of group members.')
@@ -144,13 +144,13 @@ class AdminBaseGroupForm(BaseGroupForm):
             (True,  lazy_gettext('Enabled')),
             (False, lazy_gettext('Disabled'))
         ],
-        filters = [vial.forms.str_to_bool],
-        coerce = vial.forms.str_to_bool,
+        filters = [hawat.forms.str_to_bool],
+        coerce = hawat.forms.str_to_bool,
         description = lazy_gettext('Boolean flag whether the group is enabled or disabled. Disabled groups are hidden to the most of the system features.')
     )
     managers = QuerySelectMultipleField(
         lazy_gettext('Managers:'),
-        query_factory = get_available_users,
+        query_factory = hawat.forms.get_available_users,
         get_label = format_select_option_label_user,
         blank_text = lazy_gettext('<< no selection >>'),
         description = lazy_gettext('List of users acting as group managers. These users may change various group settings.')
@@ -161,7 +161,7 @@ class AdminBaseGroupForm(BaseGroupForm):
             wtforms.validators.Optional(),
             check_parent_not_self
         ],
-        query_factory = get_available_groups,
+        query_factory = hawat.forms.get_available_groups,
         allow_blank = True,
         blank_text = lazy_gettext('<< no selection >>'),
         description = lazy_gettext('Parent group for this group. This feature enables the posibility to create structured group hierarchy.')
@@ -177,7 +177,7 @@ class AdminCreateGroupForm(AdminBaseGroupForm):
         validators = [
             wtforms.validators.DataRequired(),
             wtforms.validators.Length(min = 3, max = 100),
-            check_name_existence
+            hawat.forms.check_unique_group
         ],
         description = lazy_gettext('System-wide unique name for the group.')
     )
@@ -197,7 +197,7 @@ class AdminUpdateGroupForm(AdminBaseGroupForm):
         validators = [
             wtforms.validators.DataRequired(),
             wtforms.validators.Length(min = 3, max = 100),
-            check_name_uniqueness
+            hawat.forms.check_unique_group
         ],
         description = lazy_gettext('System-wide unique name for the group.')
     )
@@ -208,3 +208,106 @@ class AdminUpdateGroupForm(AdminBaseGroupForm):
         # Store the ID of original item in database to enable the ID uniqueness
         # check with check_name_uniqueness() validator.
         self.db_item_id = kwargs['db_item_id']
+
+
+class GroupSearchForm(hawat.forms.BaseSearchForm):
+    """
+    Class representing simple user search form.
+    """
+    search = wtforms.StringField(
+        lazy_gettext('Name, description:'),
+        validators = [
+            wtforms.validators.Optional(),
+            wtforms.validators.Length(min = 3, max = 100)
+        ],
+        description = lazy_gettext('Group`s full name or description. Search is performed even in the middle of the strings.')
+    )
+    dt_from = hawat.forms.SmartDateTimeField(
+        lazy_gettext('Creation time from:'),
+        validators = [
+            wtforms.validators.Optional()
+        ],
+        description = lazy_gettext('Lower time boundary for item creation time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences.')
+    )
+    dt_to = hawat.forms.SmartDateTimeField(
+        lazy_gettext('Creation time to:'),
+        validators = [
+            wtforms.validators.Optional()
+        ],
+        description = lazy_gettext('Upper time boundary for item creation time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences.')
+    )
+
+    state = wtforms.SelectField(
+        lazy_gettext('State:'),
+        validators = [
+            wtforms.validators.Optional(),
+        ],
+        choices = [
+            ('', lazy_gettext('Nothing selected')),
+            ('enabled',  lazy_gettext('Enabled')),
+            ('disabled', lazy_gettext('Disabled'))
+        ],
+        default = '',
+        description = lazy_gettext('Search for groups with particular state.')
+    )
+    source = wtforms.SelectField(
+        lazy_gettext('Record source:'),
+        validators = [
+            wtforms.validators.Optional()
+        ],
+        default = '',
+        description = lazy_gettext('Search for groups coming from particular sources/feeds.')
+    )
+    members = QuerySelectField(
+        lazy_gettext('Group members:'),
+        query_factory = get_available_users,
+        allow_blank = True,
+        description = lazy_gettext('Search for groups with particular members.')
+    )
+    managers = QuerySelectField(
+        lazy_gettext('Group managers:'),
+        query_factory = get_available_users,
+        allow_blank = True,
+        description = lazy_gettext('Search for groups with particular managers.')
+    )
+
+    sortby = wtforms.SelectField(
+        lazy_gettext('Sort by:'),
+        validators = [
+            wtforms.validators.Optional()
+        ],
+        choices = [
+            ('createtime.desc', lazy_gettext('by creation time descending')),
+            ('createtime.asc',  lazy_gettext('by creation time ascending')),
+            ('name.desc', lazy_gettext('by name descending')),
+            ('name.asc',  lazy_gettext('by name ascending'))
+        ],
+        default = 'name.asc'
+    )
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        #
+        # Handle additional custom keywords.
+        #
+
+        # The list of choices for 'roles' attribute comes from outside of the
+        # form to provide as loose tie as possible to the outer application.
+        # Another approach would be to load available choices here with:
+        #
+        #   roles = flask.current_app.config['ROLES']
+        #
+        # That would mean direct dependency on flask.Flask application.
+        source_list = get_available_group_sources()
+        self.source.choices = [('', lazy_gettext('Nothing selected'))] + list(zip(source_list, source_list))
+
+    @staticmethod
+    def is_multivalue(field_name):
+        """
+        Check, if given form field is a multivalue field.
+
+        :param str field_name: Name of the form field.
+        :return: ``True``, if the field can contain multiple values, ``False`` otherwise.
+        :rtype: bool
+        """
+        return False
diff --git a/lib/hawat/blueprints/groups/test/__init__.py b/lib/hawat/blueprints/groups/test/__init__.py
index 124949f05..72de72f97 100644
--- a/lib/hawat/blueprints/groups/test/__init__.py
+++ b/lib/hawat/blueprints/groups/test/__init__.py
@@ -16,14 +16,14 @@ Unit tests for :py:mod:`hawat.blueprints.groups`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.test.fixtures
-import vial.db
-from vial.test import VialTestCase, ItemCreateVialTestCase
+import hawat.test
+import hawat.test.fixtures
+import hawat.db
+from hawat.test import HawatTestCase, ItemCreateHawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class GroupsListTestCase(TestRunnerMixin, VialTestCase):
+class GroupsListTestCase(TestRunnerMixin, HawatTestCase):
     """Class for testing ``groups.list`` endpoint."""
 
     def _attempt_fail(self):
@@ -42,28 +42,28 @@ class GroupsListTestCase(TestRunnerMixin, VialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user ``user``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
 
 
-class GroupsShowTestCase(TestRunnerMixin, VialTestCase):
+class GroupsShowTestCase(TestRunnerMixin, HawatTestCase):
     """Base class for testing ``groups.show`` and ``groups.show_by_name`` endpoints."""
 
     def _attempt_fail(self, gname):
@@ -96,48 +96,48 @@ class GroupsShowTestCase(TestRunnerMixin, VialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """
         Test access as user 'user'.
 
         Only power user is able to view all available groups.
         """
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """
         Test access as user 'developer'.
 
         Only power user is able to view all available groups.
         """
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """
         Test access as user 'maintainer'.
 
         Only power user is able to view all available groups.
         """
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """
         Test access as user 'admin'.
 
         Only power user is able to view all available groups.
         """
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_B)
 
 
-class GroupsCreateTestCase(TestRunnerMixin, ItemCreateVialTestCase):
+class GroupsCreateTestCase(TestRunnerMixin, ItemCreateHawatTestCase):
     """Class for testing ``groups.create`` endpoint."""
 
     group_data_fixture = [
@@ -162,28 +162,28 @@ class GroupsCreateTestCase(TestRunnerMixin, ItemCreateVialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
         self._attempt_succeed()
 
 
-class GroupsUpdateTestCase(TestRunnerMixin, VialTestCase):
+class GroupsUpdateTestCase(TestRunnerMixin, HawatTestCase):
     """Class for testing ``groups.update`` endpoint."""
 
     def _attempt_fail(self, gname):
@@ -203,32 +203,32 @@ class GroupsUpdateTestCase(TestRunnerMixin, VialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_04_as_developer(self):
         """Test access as user 'developer'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_05_as_maintainer(self):
         """Test access as user 'maintainer'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_06_as_admin(self):
         """Test access as user 'admin'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_B)
 
 
-class GroupsEnableDisableTestCase(TestRunnerMixin, VialTestCase):
+class GroupsEnableDisableTestCase(TestRunnerMixin, HawatTestCase):
     """Class for testing ``groups.enable`` and ``groups.disable`` endpoint."""
 
     def _attempt_fail(self, gname):
@@ -279,32 +279,32 @@ class GroupsEnableDisableTestCase(TestRunnerMixin, VialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_B)
 
 
-class GroupsAddRemRejMemberTestCase(TestRunnerMixin, VialTestCase):
+class GroupsAddRemRejMemberTestCase(TestRunnerMixin, HawatTestCase):
     """Class for testing ``groups.add_member``, ``groups.reject_member`` and ``groups.remove_member`` endpoint."""
 
     def _attempt_fail(self, uname, gname):
@@ -429,7 +429,7 @@ class GroupsAddRemRejMemberTestCase(TestRunnerMixin, VialTestCase):
         )
         self.mailbox_monitoring('off')
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
         for uname in (
@@ -438,9 +438,9 @@ class GroupsAddRemRejMemberTestCase(TestRunnerMixin, VialTestCase):
                 hawat.const.ROLE_MAINTAINER,
                 hawat.const.ROLE_ADMIN
             ):
-            self._attempt_fail(uname, vial.test.fixtures.DEMO_GROUP_A)
+            self._attempt_fail(uname, hawat.test.fixtures.DEMO_GROUP_A)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
         for uname in (
@@ -448,9 +448,9 @@ class GroupsAddRemRejMemberTestCase(TestRunnerMixin, VialTestCase):
                 hawat.const.ROLE_MAINTAINER,
                 hawat.const.ROLE_ADMIN
             ):
-            self._attempt_succeed(uname, vial.test.fixtures.DEMO_GROUP_A)
+            self._attempt_succeed(uname, hawat.test.fixtures.DEMO_GROUP_A)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
         for uname in (
@@ -458,9 +458,9 @@ class GroupsAddRemRejMemberTestCase(TestRunnerMixin, VialTestCase):
                 hawat.const.ROLE_DEVELOPER,
                 hawat.const.ROLE_ADMIN
             ):
-            self._attempt_succeed(uname, vial.test.fixtures.DEMO_GROUP_A)
+            self._attempt_succeed(uname, hawat.test.fixtures.DEMO_GROUP_A)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
         for uname in (
@@ -468,10 +468,10 @@ class GroupsAddRemRejMemberTestCase(TestRunnerMixin, VialTestCase):
                 hawat.const.ROLE_DEVELOPER,
                 hawat.const.ROLE_MAINTAINER
             ):
-            self._attempt_succeed(uname, vial.test.fixtures.DEMO_GROUP_A)
+            self._attempt_succeed(uname, hawat.test.fixtures.DEMO_GROUP_A)
 
 
-class GroupsDeleteTestCase(TestRunnerMixin, VialTestCase):
+class GroupsDeleteTestCase(TestRunnerMixin, HawatTestCase):
     """Class for testing ``groups.delete`` endpoint."""
 
     def _attempt_fail(self, gname):
@@ -501,29 +501,29 @@ class GroupsDeleteTestCase(TestRunnerMixin, VialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_B)
 
 
 #-------------------------------------------------------------------------------
diff --git a/lib/hawat/blueprints/home/__init__.py b/lib/hawat/blueprints/home/__init__.py
index 114837490..1a6478c8c 100644
--- a/lib/hawat/blueprints/home/__init__.py
+++ b/lib/hawat/blueprints/home/__init__.py
@@ -29,9 +29,9 @@ __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea
 
 from flask_babel import lazy_gettext
 
-from vial.app import VialBlueprint
-from vial.view import SimpleView
-from vial.view.mixin import HTMLMixin
+from hawat.app import HawatBlueprint
+from hawat.view import SimpleView
+from hawat.view.mixin import HTMLMixin
 
 
 BLUEPRINT_NAME = 'home'
@@ -64,7 +64,7 @@ class IndexView(HTMLMixin, SimpleView):
 #-------------------------------------------------------------------------------
 
 
-class HomeBlueprint(VialBlueprint):
+class HomeBlueprint(HawatBlueprint):
     """Pluggable module - home page (*home*)."""
 
     @classmethod
@@ -77,8 +77,8 @@ class HomeBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/home/test/__init__.py b/lib/hawat/blueprints/home/test/__init__.py
index 7bbe162bb..39f1d42f4 100644
--- a/lib/hawat/blueprints/home/test/__init__.py
+++ b/lib/hawat/blueprints/home/test/__init__.py
@@ -16,13 +16,13 @@ Unit tests for :py:mod:`hawat.blueprints.home`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class HomeTestCase(TestRunnerMixin, VialTestCase):
+class HomeTestCase(TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``home.index`` endpoint.
     """
@@ -40,22 +40,22 @@ class HomeTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/hawat/blueprints/hosts/__init__.py b/lib/hawat/blueprints/hosts/__init__.py
index 0fa7da4f2..24d02b2a8 100644
--- a/lib/hawat/blueprints/hosts/__init__.py
+++ b/lib/hawat/blueprints/hosts/__init__.py
@@ -32,11 +32,11 @@ from mentat.const import tr_
 
 import hawat.events
 import hawat.const
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import BaseSearchView
-from vial.view.mixin import HTMLMixin, AJAXMixin
-from vial.utils import URLParamsBuilder
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import BaseSearchView
+from hawat.view.mixin import HTMLMixin, AJAXMixin
+from hawat.utils import URLParamsBuilder
 from hawat.base import PsycopgMixin
 from hawat.blueprints.hosts.forms import SimpleHostSearchForm
 
@@ -51,7 +51,7 @@ class AbstractSearchView(PsycopgMixin, BaseSearchView):  # pylint: disable=local
     """
     authentication = True
 
-    authorization = [vial.acl.PERMISSION_DEVELOPER]
+    authorization = [hawat.acl.PERMISSION_DEVELOPER]
 
     @classmethod
     def get_menu_title(cls, **kwargs):
@@ -128,7 +128,7 @@ class SearchView(HTMLMixin, AbstractSearchView):  # pylint: disable=locally-disa
 
     @classmethod
     def get_breadcrumbs_menu(cls):
-        breadcrumbs_menu = vial.menu.Menu()
+        breadcrumbs_menu = hawat.menu.Menu()
         breadcrumbs_menu.add_entry(
             'endpoint',
             'home',
@@ -157,7 +157,7 @@ class APISearchView(AJAXMixin, AbstractSearchView):  # pylint: disable=locally-d
 #-------------------------------------------------------------------------------
 
 
-class HostsBlueprint(VialBlueprint):
+class HostsBlueprint(HawatBlueprint):
     """Pluggable module - Host overview (*hosts*)."""
 
     @classmethod
@@ -185,8 +185,8 @@ class HostsBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/hosts/forms.py b/lib/hawat/blueprints/hosts/forms.py
index d0c908ea0..f8b486271 100644
--- a/lib/hawat/blueprints/hosts/forms.py
+++ b/lib/hawat/blueprints/hosts/forms.py
@@ -23,7 +23,7 @@ import flask_wtf
 from flask_babel import lazy_gettext
 
 import hawat.const
-import vial.forms
+import hawat.forms
 import hawat.const
 
 
@@ -35,22 +35,22 @@ class SimpleHostSearchForm(flask_wtf.FlaskForm):
         lazy_gettext('Host address:'),
         validators = [
             wtforms.validators.DataRequired(),
-            vial.forms.check_ip_record
+            hawat.forms.check_ip_record
         ]
     )
-    dt_from = vial.forms.SmartDateTimeField(
+    dt_from = hawat.forms.SmartDateTimeField(
         lazy_gettext('Detection time from:'),
         validators = [
             wtforms.validators.Optional()
         ],
-        default = lambda: vial.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
+        default = lambda: hawat.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
     )
-    dt_to = vial.forms.SmartDateTimeField(
+    dt_to = hawat.forms.SmartDateTimeField(
         lazy_gettext('Detection time to:'),
         validators = [
             wtforms.validators.Optional()
         ],
-        default = vial.forms.default_dt
+        default = hawat.forms.default_dt
     )
     submit = wtforms.SubmitField(
         lazy_gettext('Search')
diff --git a/lib/hawat/blueprints/hosts/test/__init__.py b/lib/hawat/blueprints/hosts/test/__init__.py
index d5319402f..64204754e 100644
--- a/lib/hawat/blueprints/hosts/test/__init__.py
+++ b/lib/hawat/blueprints/hosts/test/__init__.py
@@ -16,13 +16,13 @@ Unit tests for :py:mod:`hawat.blueprints.hosts`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class SearchTestCase(TestRunnerMixin, VialTestCase):
+class SearchTestCase(TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``hosts.search`` endpoint.
     """
@@ -57,22 +57,22 @@ class SearchTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail_redirect()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/hawat/blueprints/nerd/__init__.py b/lib/hawat/blueprints/nerd/__init__.py
index f8bff1322..ad1e9f120 100644
--- a/lib/hawat/blueprints/nerd/__init__.py
+++ b/lib/hawat/blueprints/nerd/__init__.py
@@ -52,13 +52,13 @@ from flask_babel import lazy_gettext
 import mentat.services.nerd
 from mentat.const import tr_
 
-import vial.db
+import hawat.db
 import hawat.const
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import RenderableView
-from vial.view.mixin import HTMLMixin, AJAXMixin, SnippetMixin
-from vial.utils import URLParamsBuilder
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import RenderableView
+from hawat.view.mixin import HTMLMixin, AJAXMixin, SnippetMixin
+from hawat.utils import URLParamsBuilder
 import hawat.const
 from hawat.blueprints.nerd.forms import NerdSearchForm
 
@@ -155,14 +155,14 @@ class SnippetSearchView(SnippetMixin, AbstractSearchView):  # pylint: disable=lo
 
     @classmethod
     def get_view_name(cls):
-        """*Implementation* of :py:func:`vial.view.BaseView.get_view_name`."""
+        """*Implementation* of :py:func:`hawat.view.BaseView.get_view_name`."""
         return 'sptsearch'
 
 
 #-------------------------------------------------------------------------------
 
 
-class NerdBlueprint(VialBlueprint):
+class NerdBlueprint(HawatBlueprint):
     """Pluggable module - NERD service (*nerd*)."""
 
     @classmethod
@@ -206,8 +206,8 @@ class NerdBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/nerd/forms.py b/lib/hawat/blueprints/nerd/forms.py
index 17dd98b06..c46222f32 100644
--- a/lib/hawat/blueprints/nerd/forms.py
+++ b/lib/hawat/blueprints/nerd/forms.py
@@ -21,7 +21,7 @@ import wtforms
 import flask_wtf
 from flask_babel import lazy_gettext
 
-import vial.forms
+import hawat.forms
 
 
 class NerdSearchForm(flask_wtf.FlaskForm):
@@ -32,7 +32,7 @@ class NerdSearchForm(flask_wtf.FlaskForm):
         lazy_gettext('Search NERD:'),
         validators = [
             wtforms.validators.DataRequired(),
-            vial.forms.check_ip4_record
+            hawat.forms.check_ip4_record
         ]
     )
     submit = wtforms.SubmitField(
diff --git a/lib/hawat/blueprints/nerd/test/__init__.py b/lib/hawat/blueprints/nerd/test/__init__.py
index 7078e92e2..76a97f6b8 100644
--- a/lib/hawat/blueprints/nerd/test/__init__.py
+++ b/lib/hawat/blueprints/nerd/test/__init__.py
@@ -16,13 +16,13 @@ Unit tests for :py:mod:`hawat.blueprints.nerd`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class SearchTestCase(TestRunnerMixin, VialTestCase):
+class SearchTestCase(TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``nerd.search`` endpoint.
     """
@@ -43,22 +43,22 @@ class SearchTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/hawat/blueprints/networks/__init__.py b/lib/hawat/blueprints/networks/__init__.py
index 487de9e87..a6540e016 100644
--- a/lib/hawat/blueprints/networks/__init__.py
+++ b/lib/hawat/blueprints/networks/__init__.py
@@ -33,10 +33,10 @@ from sqlalchemy import or_
 
 from mentat.datatype.sqldb import NetworkModel, GroupModel, ItemChangeLogModel
 
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import ItemListView, ItemShowView, ItemCreateView, ItemCreateForView, ItemUpdateView, ItemDeleteView
-from vial.view.mixin import HTMLMixin, SQLAlchemyMixin
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import ItemListView, ItemShowView, ItemCreateView, ItemCreateForView, ItemUpdateView, ItemDeleteView
+from hawat.view.mixin import HTMLMixin, SQLAlchemyMixin
 from hawat.blueprints.networks.forms import BaseNetworkForm, AdminNetworkForm, NetworkSearchForm
 
 
@@ -52,7 +52,7 @@ class ListView(HTMLMixin, SQLAlchemyMixin, ItemListView):
 
     authentication = True
 
-    authorization = [vial.acl.PERMISSION_POWER]
+    authorization = [hawat.acl.PERMISSION_POWER]
 
     @classmethod
     def get_view_title(cls, **kwargs):
@@ -64,7 +64,7 @@ class ListView(HTMLMixin, SQLAlchemyMixin, ItemListView):
 
     @classmethod
     def get_action_menu(cls):
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'create',
@@ -75,7 +75,7 @@ class ListView(HTMLMixin, SQLAlchemyMixin, ItemListView):
 
     @classmethod
     def get_context_action_menu(cls):
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'show',
@@ -172,14 +172,14 @@ class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView):
     @classmethod
     def authorize_item_action(cls, **kwargs):
         permission_mm = flask_principal.Permission(
-            vial.acl.MembershipNeed(kwargs['item'].group.id),
-            vial.acl.ManagementNeed(kwargs['item'].group.id)
+            hawat.acl.MembershipNeed(kwargs['item'].group.id),
+            hawat.acl.ManagementNeed(kwargs['item'].group.id)
         )
-        return vial.acl.PERMISSION_POWER.can() or permission_mm.can()
+        return hawat.acl.PERMISSION_POWER.can() or permission_mm.can()
 
     @classmethod
     def get_action_menu(cls):
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
 
         action_menu.add_entry(
             'endpoint',
@@ -238,7 +238,7 @@ class CreateView(HTMLMixin, SQLAlchemyMixin, ItemCreateView):  # pylint: disable
 
     @classmethod
     def authorize_item_action(cls, **kwargs):
-        return vial.acl.PERMISSION_POWER.can()
+        return hawat.acl.PERMISSION_POWER.can()
 
     @staticmethod
     def get_message_success(**kwargs):
@@ -315,9 +315,9 @@ class CreateForView(HTMLMixin, SQLAlchemyMixin, ItemCreateForView):  # pylint: d
     @classmethod
     def authorize_item_action(cls, **kwargs):
         permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].id)
+            hawat.acl.ManagementNeed(kwargs['item'].id)
         )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
 
     @staticmethod
     def get_message_success(**kwargs):
@@ -377,9 +377,9 @@ class UpdateView(HTMLMixin, SQLAlchemyMixin, ItemUpdateView):  # pylint: disable
     @classmethod
     def authorize_item_action(cls, **kwargs):
         permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].group.id)
+            hawat.acl.ManagementNeed(kwargs['item'].group.id)
         )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
 
     @staticmethod
     def get_message_success(**kwargs):
@@ -440,9 +440,9 @@ class DeleteView(HTMLMixin, SQLAlchemyMixin, ItemDeleteView):  # pylint: disable
     @classmethod
     def authorize_item_action(cls, **kwargs):
         permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].group.id)
+            hawat.acl.ManagementNeed(kwargs['item'].group.id)
         )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
 
     @staticmethod
     def get_message_success(**kwargs):
@@ -472,7 +472,7 @@ class DeleteView(HTMLMixin, SQLAlchemyMixin, ItemDeleteView):  # pylint: disable
 #-------------------------------------------------------------------------------
 
 
-class NetworksBlueprint(VialBlueprint):
+class NetworksBlueprint(HawatBlueprint):
     """Pluggable module - network management (*networks*)."""
 
     @classmethod
@@ -493,8 +493,8 @@ class NetworksBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/networks/forms.py b/lib/hawat/blueprints/networks/forms.py
index af07fb57e..2f2723556 100644
--- a/lib/hawat/blueprints/networks/forms.py
+++ b/lib/hawat/blueprints/networks/forms.py
@@ -29,9 +29,9 @@ from flask_babel import lazy_gettext
 # Custom modules.
 #
 import hawat.const
-import vial.forms
-import vial.db
-from vial.forms import get_available_groups
+import hawat.forms
+import hawat.db
+from hawat.forms import get_available_groups
 
 from mentat.datatype.sqldb import NetworkModel
 
@@ -40,14 +40,14 @@ def get_available_sources():
     """
     Query the database for list of network record sources.
     """
-    result = vial.db.db_query(NetworkModel)\
+    result = hawat.db.db_query(NetworkModel)\
         .distinct(NetworkModel.source)\
         .order_by(NetworkModel.source)\
         .all()
     return [x.source for x in result]
 
 
-class BaseNetworkForm(vial.forms.BaseItemForm):
+class BaseNetworkForm(hawat.forms.BaseItemForm):
     """
     Class representing base network record form.
     """
@@ -69,7 +69,7 @@ class BaseNetworkForm(vial.forms.BaseItemForm):
         lazy_gettext('Network:'),
         validators = [
             wtforms.validators.DataRequired(),
-            vial.forms.check_network_record
+            hawat.forms.check_network_record
         ]
     )
     rank = wtforms.IntegerField(
@@ -101,7 +101,7 @@ class AdminNetworkForm(BaseNetworkForm):
     )
 
 
-class NetworkSearchForm(vial.forms.BaseSearchForm):
+class NetworkSearchForm(hawat.forms.BaseSearchForm):
     """
     Class representing simple user search form.
     """
@@ -113,14 +113,14 @@ class NetworkSearchForm(vial.forms.BaseSearchForm):
         ],
         description = lazy_gettext('Network`s name, address or description. Search is performed even in the middle of the strings.')
     )
-    dt_from = vial.forms.SmartDateTimeField(
+    dt_from = hawat.forms.SmartDateTimeField(
         lazy_gettext('Creation time from:'),
         validators = [
             wtforms.validators.Optional()
         ],
         description = lazy_gettext('Lower time boundary for item creation time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences.')
     )
-    dt_to = vial.forms.SmartDateTimeField(
+    dt_to = hawat.forms.SmartDateTimeField(
         lazy_gettext('Creation time to:'),
         validators = [
             wtforms.validators.Optional()
diff --git a/lib/hawat/blueprints/networks/test/__init__.py b/lib/hawat/blueprints/networks/test/__init__.py
index a7e13c05e..045ea3617 100644
--- a/lib/hawat/blueprints/networks/test/__init__.py
+++ b/lib/hawat/blueprints/networks/test/__init__.py
@@ -18,10 +18,10 @@ import unittest
 from mentat.datatype.sqldb import NetworkModel
 
 import hawat.const
-import vial.test
-import vial.test.fixtures
-import vial.db
-from vial.test import VialTestCase, ItemCreateVialTestCase
+import hawat.test
+import hawat.test.fixtures
+import hawat.db
+from hawat.test import HawatTestCase, ItemCreateHawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
@@ -39,20 +39,20 @@ class NetworkTestMixin:
         Get given network.
         """
         if not with_app_context:
-            return vial.db.db_session().query(NetworkModel).filter(NetworkModel.netname == network_name).one_or_none()
+            return hawat.db.db_session().query(NetworkModel).filter(NetworkModel.netname == network_name).one_or_none()
         with self.app.app_context():
-            return vial.db.db_session().query(NetworkModel).filter(NetworkModel.netname == network_name).one_or_none()
+            return hawat.db.db_session().query(NetworkModel).filter(NetworkModel.netname == network_name).one_or_none()
 
     def network_save(self, network_object, with_app_context = False):
         """
         Update given network.
         """
         if not with_app_context:
-            vial.db.db_session().add(network_object)
-            vial.db.db_session().commit()
+            hawat.db.db_session().add(network_object)
+            hawat.db.db_session().commit()
         with self.app.app_context():
-            vial.db.db_session().add(network_object)
-            vial.db.db_session().commit()
+            hawat.db.db_session().add(network_object)
+            hawat.db.db_session().commit()
 
     def network_id(self, network_type, with_app_context = False):
         """
@@ -66,7 +66,7 @@ class NetworkTestMixin:
             return fobj.id
 
 
-class NetworksListTestCase(TestRunnerMixin, VialTestCase):
+class NetworksListTestCase(TestRunnerMixin, HawatTestCase):
     """Class for testing ``networks.list`` endpoint."""
 
     def _attempt_fail(self):
@@ -85,28 +85,28 @@ class NetworksListTestCase(TestRunnerMixin, VialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user ``user``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
 
 
-class NetworksShowTestCase(NetworkTestMixin, TestRunnerMixin, VialTestCase):
+class NetworksShowTestCase(NetworkTestMixin, TestRunnerMixin, HawatTestCase):
     """Base class for testing ``networks.show`` endpoint."""
 
     def _attempt_fail(self, nname):
@@ -127,48 +127,48 @@ class NetworksShowTestCase(NetworkTestMixin, TestRunnerMixin, VialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """
         Test access as user 'user'.
 
         Only power user is able to view all available networks.
         """
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._nname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._nname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """
         Test access as user 'developer'.
 
         Only power user is able to view all available networks.
         """
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._nname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._nname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """
         Test access as user 'maintainer'.
 
         Only power user is able to view all available networks.
         """
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """
         Test access as user 'admin'.
 
         Only power user is able to view all available networks.
         """
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_B))
 
 
-class NetworksCreateTestCase(NetworkTestMixin, TestRunnerMixin, ItemCreateVialTestCase):
+class NetworksCreateTestCase(NetworkTestMixin, TestRunnerMixin, ItemCreateHawatTestCase):
     """Class for testing ``networks.create`` endpoint."""
 
     network_data_fixture = [
@@ -195,28 +195,28 @@ class NetworksCreateTestCase(NetworkTestMixin, TestRunnerMixin, ItemCreateVialTe
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
         self._attempt_succeed()
 
 
-class NetworksCreateForTestCase(NetworkTestMixin, TestRunnerMixin, ItemCreateVialTestCase):
+class NetworksCreateForTestCase(NetworkTestMixin, TestRunnerMixin, ItemCreateHawatTestCase):
     """Class for testing ``networks.createfor`` endpoint."""
 
     network_data_fixture = [
@@ -244,32 +244,32 @@ class NetworksCreateForTestCase(NetworkTestMixin, TestRunnerMixin, ItemCreateVia
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_fail(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_B)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_A)
+        self._attempt_succeed(hawat.test.fixtures.DEMO_GROUP_B)
 
 
-class NetworksUpdateTestCase(NetworkTestMixin, TestRunnerMixin, VialTestCase):
+class NetworksUpdateTestCase(NetworkTestMixin, TestRunnerMixin, HawatTestCase):
     """Class for testing ``networks.update`` endpoint."""
 
     def _attempt_fail(self, nname):
@@ -289,32 +289,32 @@ class NetworksUpdateTestCase(NetworkTestMixin, TestRunnerMixin, VialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
-        self._attempt_fail(self._nname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._nname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_fail(self._nname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._nname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_04_as_developer(self):
         """Test access as user 'developer'."""
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._nname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._nname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_05_as_maintainer(self):
         """Test access as user 'maintainer'."""
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_06_as_admin(self):
         """Test access as user 'admin'."""
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_B))
 
 
-class NetworksDeleteTestCase(NetworkTestMixin, TestRunnerMixin, VialTestCase):
+class NetworksDeleteTestCase(NetworkTestMixin, TestRunnerMixin, HawatTestCase):
     """Class for testing ``networks.delete`` endpoint."""
 
     def _attempt_fail(self, nname):
@@ -344,29 +344,29 @@ class NetworksDeleteTestCase(NetworkTestMixin, TestRunnerMixin, VialTestCase):
             ]
         )
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
-        self._attempt_fail(self._nname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._nname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_fail(self._nname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._nname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_fail(self._nname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_fail(self._nname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_B))
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_A))
-        self._attempt_succeed(self._nname(vial.test.fixtures.DEMO_GROUP_B))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_A))
+        self._attempt_succeed(self._nname(hawat.test.fixtures.DEMO_GROUP_B))
 
 
 #-------------------------------------------------------------------------------
diff --git a/lib/hawat/blueprints/pdnsr/__init__.py b/lib/hawat/blueprints/pdnsr/__init__.py
index 043968e48..4bc6fb9ac 100644
--- a/lib/hawat/blueprints/pdnsr/__init__.py
+++ b/lib/hawat/blueprints/pdnsr/__init__.py
@@ -52,13 +52,13 @@ from flask_babel import lazy_gettext
 import mentat.services.pdnsr
 from mentat.const import tr_
 
-import vial.db
+import hawat.db
 import hawat.const
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import RenderableView
-from vial.view.mixin import HTMLMixin, AJAXMixin, SnippetMixin
-from vial.utils import URLParamsBuilder
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import RenderableView
+from hawat.view.mixin import HTMLMixin, AJAXMixin, SnippetMixin
+from hawat.utils import URLParamsBuilder
 import hawat.const
 from hawat.blueprints.pdnsr.forms import PDNSRSearchForm
 
@@ -166,7 +166,7 @@ class SnippetSearchView(SnippetMixin, AbstractSearchView):  # pylint: disable=lo
 #-------------------------------------------------------------------------------
 
 
-class PDNSRBlueprint(VialBlueprint):
+class PDNSRBlueprint(HawatBlueprint):
     """Pluggable module - PassiveDNS service (*pdnsr*)."""
 
     @classmethod
@@ -215,8 +215,8 @@ class PDNSRBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/pdnsr/forms.py b/lib/hawat/blueprints/pdnsr/forms.py
index dd8bfe4f7..6719b362f 100644
--- a/lib/hawat/blueprints/pdnsr/forms.py
+++ b/lib/hawat/blueprints/pdnsr/forms.py
@@ -21,7 +21,7 @@ import wtforms
 import flask_wtf
 from flask_babel import lazy_gettext
 
-import vial.forms
+import hawat.forms
 
 
 class PDNSRSearchForm(flask_wtf.FlaskForm):
@@ -32,7 +32,7 @@ class PDNSRSearchForm(flask_wtf.FlaskForm):
         lazy_gettext('Search PassiveDNS:'),
         validators = [
             wtforms.validators.DataRequired(),
-            vial.forms.check_ip_record
+            hawat.forms.check_ip_record
         ]
     )
     sortby = wtforms.SelectField(
diff --git a/lib/hawat/blueprints/pdnsr/test/__init__.py b/lib/hawat/blueprints/pdnsr/test/__init__.py
index f88252ddf..cdd5534f5 100644
--- a/lib/hawat/blueprints/pdnsr/test/__init__.py
+++ b/lib/hawat/blueprints/pdnsr/test/__init__.py
@@ -16,13 +16,13 @@ Unit tests for :py:mod:`hawat.blueprints.pdnsr`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class SearchTestCase(TestRunnerMixin, VialTestCase):
+class SearchTestCase(TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``pdnsr.search`` endpoint.
     """
@@ -43,22 +43,22 @@ class SearchTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/hawat/blueprints/performance/__init__.py b/lib/hawat/blueprints/performance/__init__.py
index da0af0808..cb87e8b4c 100644
--- a/lib/hawat/blueprints/performance/__init__.py
+++ b/lib/hawat/blueprints/performance/__init__.py
@@ -25,9 +25,9 @@ from flask_babel import gettext, lazy_gettext
 import mentat.const
 import mentat.stats.rrd
 
-from vial.app import VialBlueprint
-from vial.view import SimpleView, FileNameView
-from vial.view.mixin import HTMLMixin
+from hawat.app import HawatBlueprint
+from hawat.view import SimpleView, FileNameView
+from hawat.view.mixin import HTMLMixin
 
 
 BLUEPRINT_NAME = 'performance'
@@ -130,7 +130,7 @@ class RRDDBView(FileNameView):
 #-------------------------------------------------------------------------------
 
 
-class PerformanceBlueprint(VialBlueprint):
+class PerformanceBlueprint(HawatBlueprint):
     """Pluggable module - system processing performance (*performance*)."""
 
     @classmethod
@@ -152,8 +152,8 @@ class PerformanceBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/performance/test/__init__.py b/lib/hawat/blueprints/performance/test/__init__.py
index 9e577c6cb..02eae1787 100644
--- a/lib/hawat/blueprints/performance/test/__init__.py
+++ b/lib/hawat/blueprints/performance/test/__init__.py
@@ -16,13 +16,13 @@ Unit tests for :py:mod:`hawat.blueprints.performance`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class SearchTestCase(TestRunnerMixin, VialTestCase):
+class SearchTestCase(TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``performance.view`` endpoint.
     """
@@ -51,22 +51,22 @@ class SearchTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail_redirect()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/hawat/blueprints/reports/__init__.py b/lib/hawat/blueprints/reports/__init__.py
index a866a1727..22ad4c529 100644
--- a/lib/hawat/blueprints/reports/__init__.py
+++ b/lib/hawat/blueprints/reports/__init__.py
@@ -39,12 +39,12 @@ from mentat.const import tr_
 
 import hawat.const
 
-import vial.menu
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import RenderableView, FileIdView, BaseSearchView, ItemShowView, ItemDeleteView
-from vial.view.mixin import HTMLMixin, AJAXMixin, SQLAlchemyMixin
-from vial.utils import URLParamsBuilder
+import hawat.menu
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import RenderableView, FileIdView, BaseSearchView, ItemShowView, ItemDeleteView
+from hawat.view.mixin import HTMLMixin, AJAXMixin, SQLAlchemyMixin
+from hawat.utils import URLParamsBuilder
 from hawat.blueprints.reports.forms import EventReportSearchForm, ReportingDashboardForm, \
     FeedbackForm
 
@@ -187,16 +187,16 @@ class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView):
     def authorize_item_action(cls, **kwargs):
         for group in kwargs['item'].groups:
             permission_mm = flask_principal.Permission(
-                vial.acl.MembershipNeed(group.id),
-                vial.acl.ManagementNeed(group.id)
+                hawat.acl.MembershipNeed(group.id),
+                hawat.acl.ManagementNeed(group.id)
             )
             if permission_mm.can():
                 return permission_mm.can()
-        return vial.acl.PERMISSION_POWER.can()
+        return hawat.acl.PERMISSION_POWER.can()
 
     @classmethod
     def get_breadcrumbs_menu(cls):  # pylint: disable=locally-disabled,unused-argument
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'home',
@@ -216,7 +216,7 @@ class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView):
 
     @classmethod
     def get_action_menu(cls):
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'search',
@@ -464,7 +464,7 @@ class DeleteView(HTMLMixin, SQLAlchemyMixin, ItemDeleteView):  # pylint: disable
 
     authentication = True
 
-    authorization = [vial.acl.PERMISSION_ADMIN]
+    authorization = [hawat.acl.PERMISSION_ADMIN]
 
     @classmethod
     def get_menu_legend(cls, **kwargs):
@@ -568,7 +568,7 @@ class FeedbackView(AJAXMixin, RenderableView):
 #-------------------------------------------------------------------------------
 
 
-class ReportsBlueprint(VialBlueprint):
+class ReportsBlueprint(HawatBlueprint):
     """Pluggable module - periodical event reports (*reports*)."""
 
     @classmethod
@@ -620,8 +620,8 @@ class ReportsBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/reports/forms.py b/lib/hawat/blueprints/reports/forms.py
index 0803a09c5..0bf10cae9 100644
--- a/lib/hawat/blueprints/reports/forms.py
+++ b/lib/hawat/blueprints/reports/forms.py
@@ -27,8 +27,8 @@ from flask_babel import lazy_gettext
 import mentat.const
 from mentat.datatype.sqldb import UserModel, GroupModel
 import hawat.const
-import vial.forms
-import vial.db
+import hawat.forms
+import hawat.db
 import hawat.const
 
 
@@ -38,11 +38,11 @@ def get_available_groups():
     """
     # In case current user is administrator provide list of all groups.
     if flask_login.current_user.has_role(hawat.const.ROLE_ADMIN):
-        return vial.db.db_query(GroupModel).\
+        return hawat.db.db_query(GroupModel).\
             order_by(GroupModel.name).\
             all()
     # Otherwise provide only list of groups current user is member of.
-    return vial.db.db_query(GroupModel).\
+    return hawat.db.db_query(GroupModel).\
         filter(GroupModel.members.any(UserModel.id == flask_login.current_user.id)).\
         order_by(GroupModel.name).\
         all()
@@ -69,7 +69,7 @@ def get_type_choices():
         )
     )
 
-class EventReportSearchForm(vial.forms.BaseSearchForm):
+class EventReportSearchForm(hawat.forms.BaseSearchForm):
     """
     Class representing event report search form.
     """
@@ -101,19 +101,19 @@ class EventReportSearchForm(vial.forms.BaseSearchForm):
         choices = get_type_choices(),
         filters = [lambda x: x or []]
     )
-    dt_from = vial.forms.SmartDateTimeField(
+    dt_from = hawat.forms.SmartDateTimeField(
         lazy_gettext('From:'),
         validators = [
             wtforms.validators.Optional()
         ],
-        default = lambda: vial.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
+        default = lambda: hawat.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
     )
-    dt_to = vial.forms.SmartDateTimeField(
+    dt_to = hawat.forms.SmartDateTimeField(
         lazy_gettext('To:'),
         validators = [
             wtforms.validators.Optional()
         ],
-        default = vial.forms.default_dt
+        default = hawat.forms.default_dt
     )
 
     @staticmethod
@@ -140,19 +140,19 @@ class ReportingDashboardForm(flask_wtf.FlaskForm):
         allow_blank = False,
         get_pk = lambda item: item.name
     )
-    dt_from = vial.forms.SmartDateTimeField(
+    dt_from = hawat.forms.SmartDateTimeField(
         lazy_gettext('From:'),
         validators = [
             wtforms.validators.Optional()
         ],
-        default = lambda: vial.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
+        default = lambda: hawat.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
     )
-    dt_to = vial.forms.SmartDateTimeField(
+    dt_to = hawat.forms.SmartDateTimeField(
         lazy_gettext('To:'),
         validators = [
             wtforms.validators.Optional()
         ],
-        default = vial.forms.default_dt
+        default = hawat.forms.default_dt
     )
     submit = wtforms.SubmitField(
         lazy_gettext('Search')
@@ -179,7 +179,7 @@ class FeedbackForm(flask_wtf.FlaskForm):
     ip = wtforms.HiddenField(
         validators=[
             wtforms.validators.DataRequired(),
-            vial.forms.check_network_record
+            hawat.forms.check_network_record
         ]
     )
     text = wtforms.TextAreaField(
diff --git a/lib/hawat/blueprints/reports/test/__init__.py b/lib/hawat/blueprints/reports/test/__init__.py
index e0e79969d..e5022864b 100644
--- a/lib/hawat/blueprints/reports/test/__init__.py
+++ b/lib/hawat/blueprints/reports/test/__init__.py
@@ -16,13 +16,13 @@ Unit tests for :py:mod:`hawat.blueprints.reports`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class SearchTestCase(TestRunnerMixin, VialTestCase):
+class SearchTestCase(TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``reports.view`` endpoint.
     """
@@ -51,22 +51,22 @@ class SearchTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail_redirect()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/hawat/blueprints/settings_reporting/__init__.py b/lib/hawat/blueprints/settings_reporting/__init__.py
index 039ee2ffa..0afde339a 100644
--- a/lib/hawat/blueprints/settings_reporting/__init__.py
+++ b/lib/hawat/blueprints/settings_reporting/__init__.py
@@ -30,10 +30,10 @@ from flask_babel import gettext, lazy_gettext
 import mentat.reports.utils
 from mentat.datatype.sqldb import SettingsReportingModel, ItemChangeLogModel
 
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import ItemShowView, ItemCreateView, ItemUpdateView
-from vial.view.mixin import HTMLMixin, SQLAlchemyMixin
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import ItemShowView, ItemCreateView, ItemUpdateView
+from hawat.view.mixin import HTMLMixin, SQLAlchemyMixin
 from hawat.blueprints.settings_reporting.forms import CreateSettingsReportingForm,\
     UpdateSettingsReportingForm
 
@@ -65,14 +65,14 @@ class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView):
     @classmethod
     def authorize_item_action(cls, **kwargs):
         permission_mm = flask_principal.Permission(
-            vial.acl.MembershipNeed(kwargs['item'].group.id),
-            vial.acl.ManagementNeed(kwargs['item'].group.id)
+            hawat.acl.MembershipNeed(kwargs['item'].group.id),
+            hawat.acl.ManagementNeed(kwargs['item'].group.id)
         )
-        return vial.acl.PERMISSION_POWER.can() or permission_mm.can()
+        return hawat.acl.PERMISSION_POWER.can() or permission_mm.can()
 
     @classmethod
     def get_breadcrumbs_menu(cls):  # pylint: disable=locally-disabled,unused-argument
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
 
         action_menu.add_entry(
             'endpoint',
@@ -99,7 +99,7 @@ class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView):
 
     @classmethod
     def get_action_menu(cls):
-        action_menu = vial.menu.Menu()
+        action_menu = hawat.menu.Menu()
         action_menu.add_entry(
             'endpoint',
             'showgroup',
@@ -164,9 +164,9 @@ class CreateView(HTMLMixin, SQLAlchemyMixin, ItemCreateView):  # pylint: disable
     @classmethod
     def authorize_item_action(cls, **kwargs):
         permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].group.id)
+            hawat.acl.ManagementNeed(kwargs['item'].group.id)
         )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
 
     @staticmethod
     def get_message_success(**kwargs):
@@ -225,9 +225,9 @@ class UpdateView(HTMLMixin, SQLAlchemyMixin, ItemUpdateView):  # pylint: disable
     @classmethod
     def authorize_item_action(cls, **kwargs):
         permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].group.id)
+            hawat.acl.ManagementNeed(kwargs['item'].group.id)
         )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
 
     @staticmethod
     def get_message_success(**kwargs):
@@ -261,7 +261,7 @@ class UpdateView(HTMLMixin, SQLAlchemyMixin, ItemUpdateView):  # pylint: disable
 #-------------------------------------------------------------------------------
 
 
-class SettingsReportingBlueprint(VialBlueprint):
+class SettingsReportingBlueprint(HawatBlueprint):
     """Pluggable module - reporting settings. (*settings_reporting*)"""
 
     @classmethod
@@ -274,8 +274,8 @@ class SettingsReportingBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/settings_reporting/forms.py b/lib/hawat/blueprints/settings_reporting/forms.py
index 577235986..5082b7512 100644
--- a/lib/hawat/blueprints/settings_reporting/forms.py
+++ b/lib/hawat/blueprints/settings_reporting/forms.py
@@ -26,8 +26,8 @@ from babel import Locale
 from flask_babel import lazy_gettext
 
 import hawat.const
-import vial.forms
-import vial.db
+import hawat.forms
+import hawat.db
 
 import mentat.const
 from mentat.datatype.sqldb import GroupModel
@@ -40,7 +40,7 @@ def get_available_groups():
     """
     Query the database for list of all available groups.
     """
-    return vial.db.db_query(GroupModel).order_by(GroupModel.name).all()
+    return hawat.db.db_query(GroupModel).order_by(GroupModel.name).all()
 
 
 def get_available_locales():
@@ -65,7 +65,7 @@ def get_available_locales():
     return locale_list
 
 
-class BaseSettingsReportingForm(vial.forms.BaseItemForm):
+class BaseSettingsReportingForm(hawat.forms.BaseItemForm):
     """
     Class representing base reporting settings form.
     """
@@ -83,35 +83,35 @@ class BaseSettingsReportingForm(vial.forms.BaseItemForm):
         ],
         filters = [lambda x: x or None]
     )
-    emails_low = vial.forms.CommaListField(
+    emails_low = hawat.forms.CommaListField(
         lazy_gettext('Target emails - low severity:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_email_list
+            hawat.forms.check_email_list
         ]
     )
-    emails_medium = vial.forms.CommaListField(
+    emails_medium = hawat.forms.CommaListField(
         lazy_gettext('Target emails - medium severity:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_email_list
+            hawat.forms.check_email_list
         ]
     )
-    emails_high = vial.forms.CommaListField(
+    emails_high = hawat.forms.CommaListField(
         lazy_gettext('Target emails - high severity:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_email_list
+            hawat.forms.check_email_list
         ]
     )
-    emails_critical = vial.forms.CommaListField(
+    emails_critical = hawat.forms.CommaListField(
         lazy_gettext('Target emails - critical severity:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_email_list
+            hawat.forms.check_email_list
         ]
     )
-    redirect = vial.forms.RadioFieldWithNone(
+    redirect = hawat.forms.RadioFieldWithNone(
         lazy_gettext('Report redirection:'),
         validators = [
             wtforms.validators.Optional(),
@@ -121,8 +121,8 @@ class BaseSettingsReportingForm(vial.forms.BaseItemForm):
             (True,  lazy_gettext('Enabled')),
             (False, lazy_gettext('Disabled'))
         ],
-        filters = [vial.forms.str_to_bool_with_none],
-        coerce = vial.forms.str_to_bool_with_none
+        filters = [hawat.forms.str_to_bool_with_none],
+        coerce = hawat.forms.str_to_bool_with_none
     )
     locale = wtforms.SelectField(
         lazy_gettext('Locale:'),
diff --git a/lib/hawat/blueprints/skeleton/__init__.py b/lib/hawat/blueprints/skeleton/__init__.py
index 6522f3786..5f1b2f25d 100644
--- a/lib/hawat/blueprints/skeleton/__init__.py
+++ b/lib/hawat/blueprints/skeleton/__init__.py
@@ -20,9 +20,9 @@ __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea
 import flask_login
 from flask_babel import lazy_gettext
 
-from vial.app import VialBlueprint
-from vial.view import SimpleView
-from vial.view.mixin import HTMLMixin
+from hawat.app import HawatBlueprint
+from hawat.view import SimpleView
+from hawat.view.mixin import HTMLMixin
 
 
 BLUEPRINT_NAME = 'skeleton'
@@ -63,7 +63,7 @@ class ExampleView(HTMLMixin, SimpleView):
 #-------------------------------------------------------------------------------
 
 
-class SkeletonBlueprint(VialBlueprint):
+class SkeletonBlueprint(HawatBlueprint):
     """Pluggable module - skeleton (*skeleton*)."""
 
     @classmethod
@@ -84,8 +84,8 @@ class SkeletonBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/status/__init__.py b/lib/hawat/blueprints/status/__init__.py
index 1c5d34b4d..d7effc1dd 100644
--- a/lib/hawat/blueprints/status/__init__.py
+++ b/lib/hawat/blueprints/status/__init__.py
@@ -37,10 +37,10 @@ from flask_babel import gettext, lazy_gettext
 
 import pyzenkit.jsonconf
 import mentat.system
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import SimpleView
-from vial.view.mixin import HTMLMixin
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import SimpleView
+from hawat.view.mixin import HTMLMixin
 
 
 BLUEPRINT_NAME = 'status'
@@ -53,7 +53,7 @@ class ViewView(HTMLMixin, SimpleView):
     """
     authentication = True
 
-    authorization = [vial.acl.PERMISSION_ADMIN]
+    authorization = [hawat.acl.PERMISSION_ADMIN]
 
     @classmethod
     def get_view_name(cls):
@@ -111,7 +111,7 @@ class ViewView(HTMLMixin, SimpleView):
 #-------------------------------------------------------------------------------
 
 
-class StatusBlueprint(VialBlueprint):
+class StatusBlueprint(HawatBlueprint):
     """Pluggable module - Mentat system status (*status*)."""
 
     @classmethod
@@ -133,8 +133,8 @@ class StatusBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/status/test/__init__.py b/lib/hawat/blueprints/status/test/__init__.py
index 04906c988..84806c8fb 100644
--- a/lib/hawat/blueprints/status/test/__init__.py
+++ b/lib/hawat/blueprints/status/test/__init__.py
@@ -16,14 +16,14 @@ Unit tests for :py:mod:`hawat.blueprints.status`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
+import hawat.test
+import hawat.db
 
-from vial.test import VialTestCase
+from hawat.test import HawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class StatusViewTestCase(TestRunnerMixin, VialTestCase):
+class StatusViewTestCase(TestRunnerMixin, HawatTestCase):
     """Class for testing ``status.view`` endpoint."""
 
     def _attempt_fail_redirect(self):
@@ -56,22 +56,22 @@ class StatusViewTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail_redirect()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/hawat/blueprints/timeline/__init__.py b/lib/hawat/blueprints/timeline/__init__.py
index 18655020a..76805df50 100644
--- a/lib/hawat/blueprints/timeline/__init__.py
+++ b/lib/hawat/blueprints/timeline/__init__.py
@@ -33,11 +33,11 @@ from mentat.services.eventstorage import QTYPE_TIMELINE
 
 import hawat.events
 import hawat.const
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import BaseSearchView, CustomSearchView
-from vial.view.mixin import HTMLMixin, AJAXMixin
-from vial.utils import URLParamsBuilder
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import BaseSearchView, CustomSearchView
+from hawat.view.mixin import HTMLMixin, AJAXMixin
+from hawat.utils import URLParamsBuilder
 from hawat.base import PsycopgMixin
 from hawat.blueprints.timeline.forms import SimpleTimelineSearchForm
 
@@ -89,8 +89,8 @@ def _get_search_form(request_args = None):
 
     # In case no time bounds were set adjust them manually.
     if request_args and not ('dt_from' in request_args or 'dt_to' in request_args or 'st_from' in request_args or 'st_to' in request_args):
-        form.dt_from.process_data(vial.forms.default_dt_with_delta())
-        form.dt_to.process_data(vial.forms.default_dt())
+        form.dt_from.process_data(hawat.forms.default_dt_with_delta())
+        form.dt_to.process_data(hawat.forms.default_dt())
 
     return form
 
@@ -342,7 +342,7 @@ class SearchView(HTMLMixin, AbstractSearchView):  # pylint: disable=locally-disa
 
     @classmethod
     def get_breadcrumbs_menu(cls):
-        breadcrumbs_menu = vial.menu.Menu()
+        breadcrumbs_menu = hawat.menu.Menu()
         breadcrumbs_menu.add_entry(
             'endpoint',
             'home',
@@ -456,7 +456,7 @@ class APILegacySearchView(AJAXMixin, AbstractSearchView):  # pylint: disable=loc
 #-------------------------------------------------------------------------------
 
 
-class TimelineBlueprint(VialBlueprint):
+class TimelineBlueprint(HawatBlueprint):
     """Pluggable module - IDEA event timelines (*timeline*)."""
 
     @classmethod
@@ -486,8 +486,8 @@ class TimelineBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/timeline/forms.py b/lib/hawat/blueprints/timeline/forms.py
index 6cf58bea8..e7adcdb70 100644
--- a/lib/hawat/blueprints/timeline/forms.py
+++ b/lib/hawat/blueprints/timeline/forms.py
@@ -26,8 +26,8 @@ from flask_babel import lazy_gettext
 
 from mentat.datatype.sqldb import GroupModel
 import hawat.const
-import vial.forms
-import vial.db
+import hawat.forms
+import hawat.db
 import hawat.const
 
 
@@ -35,91 +35,91 @@ def get_available_groups():
     """
     Query the database for list of all available groups.
     """
-    return vial.db.db_query(GroupModel).order_by(GroupModel.name).all()
+    return hawat.db.db_query(GroupModel).order_by(GroupModel.name).all()
 
 
 class SimpleTimelineSearchForm(flask_wtf.FlaskForm):
     """
     Class representing simple event timeline search form.
     """
-    dt_from = vial.forms.SmartDateTimeField(
+    dt_from = hawat.forms.SmartDateTimeField(
         lazy_gettext('Detection time from:'),
         validators = [
             wtforms.validators.Optional()
         ],
         description = lazy_gettext('Lower time boundary for event detection time as provided by event detector. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences. Event detectors are usually outside of the control of Mentat system administrators and may sometimes emit events with invalid detection times, for example timestamps in the future.'),
-        default = lambda: vial.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
+        default = lambda: hawat.forms.default_dt_with_delta(hawat.const.DEFAULT_RESULT_TIMEDELTA)
     )
-    dt_to = vial.forms.SmartDateTimeField(
+    dt_to = hawat.forms.SmartDateTimeField(
         lazy_gettext('Detection time to:'),
         validators = [
             wtforms.validators.Optional()
         ],
         description = lazy_gettext('Upper time boundary for event detection time as provided by event detector. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences. Event detectors are usually outside of the control of Mentat system administrators and may sometimes emit events with invalid detection times, for example timestamps in the future.'),
-        default = vial.forms.default_dt
+        default = hawat.forms.default_dt
     )
-    st_from = vial.forms.SmartDateTimeField(
+    st_from = hawat.forms.SmartDateTimeField(
         lazy_gettext('Storage time from:'),
         validators = [
             wtforms.validators.Optional()
         ],
         description = lazy_gettext('Lower time boundary for event storage time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences. Event storage time is provided by Mentat system itself. It is a timestamp of the exact moment the event was stored into the database.')
     )
-    st_to = vial.forms.SmartDateTimeField(
+    st_to = hawat.forms.SmartDateTimeField(
         lazy_gettext('Storage time to:'),
         validators = [
             wtforms.validators.Optional()
         ],
         description = lazy_gettext('Upper time boundary for event storage time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences. Event storage time is provided by Mentat system itself. It is a timestamp of the exact moment the event was stored into the database.')
     )
-    source_addrs = vial.forms.CommaListField(
+    source_addrs = hawat.forms.CommaListField(
         lazy_gettext('Source addresses:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_network_record_list
+            hawat.forms.check_network_record_list
         ],
         widget = wtforms.widgets.TextArea(),
         description = lazy_gettext('Comma separated list of event source IP4/6 addresses, ranges or networks. In this context a source does not necessarily mean a source of the connection, but rather a source of the problem as reported by a detector. Any additional whitespace is ignored and may be used for better readability.')
     )
-    target_addrs = vial.forms.CommaListField(
+    target_addrs = hawat.forms.CommaListField(
         lazy_gettext('Target addresses:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_network_record_list
+            hawat.forms.check_network_record_list
         ],
         widget = wtforms.widgets.TextArea(),
         description = lazy_gettext('Comma separated list of event target IP4/6 addresses, ranges or networks. In this context a target does not necessarily mean a target of the connection, but rather a victim of the problem as reported by a detector. Any additional whitespace is ignored and may be used for better readability.')
     )
-    host_addrs = vial.forms.CommaListField(
+    host_addrs = hawat.forms.CommaListField(
         lazy_gettext('Host addresses:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_network_record_list
+            hawat.forms.check_network_record_list
         ],
         widget = wtforms.widgets.TextArea(),
         description = lazy_gettext('Comma separated list of event source or target IP4/6 addresses, ranges or networks. Any additional whitespace is ignored and may be used for better readability.')
     )
-    source_ports = vial.forms.CommaListField(
+    source_ports = hawat.forms.CommaListField(
         lazy_gettext('Source ports:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_port_list
+            hawat.forms.check_port_list
         ],
         description = lazy_gettext('Comma separated list of source ports as integers. In this context a source does not necessarily mean a source of the connection, but rather a source of the problem as reported by a detector. Any additional whitespace is ignored and may be used for better readability.')
     )
-    target_ports = vial.forms.CommaListField(
+    target_ports = hawat.forms.CommaListField(
         lazy_gettext('Target ports:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_port_list
+            hawat.forms.check_port_list
         ],
         description = lazy_gettext('Comma separated list of target ports as integers. In this context a target does not necessarily mean a target of the connection, but rather a victim of the problem as reported by a detector. Any additional whitespace is ignored and may be used for better readability.')
     )
-    host_ports = vial.forms.CommaListField(
+    host_ports = hawat.forms.CommaListField(
         lazy_gettext('Host ports:'),
         validators = [
             wtforms.validators.Optional(),
-            vial.forms.check_port_list
+            hawat.forms.check_port_list
         ],
         description = lazy_gettext('Comma separated list of source or target ports as integers. Any additional whitespace is ignored and may be used for better readability.')
     )
diff --git a/lib/hawat/blueprints/timeline/test/__init__.py b/lib/hawat/blueprints/timeline/test/__init__.py
index eb0e5294f..7d1c7b383 100644
--- a/lib/hawat/blueprints/timeline/test/__init__.py
+++ b/lib/hawat/blueprints/timeline/test/__init__.py
@@ -16,13 +16,13 @@ Unit tests for :py:mod:`hawat.blueprints.timeline`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class SearchTestCase(TestRunnerMixin, VialTestCase):
+class SearchTestCase(TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``timeline.search`` endpoint.
     """
@@ -51,22 +51,22 @@ class SearchTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail_redirect()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/hawat/blueprints/users/__init__.py b/lib/hawat/blueprints/users/__init__.py
index ab21b608c..ba70c1aaa 100644
--- a/lib/hawat/blueprints/users/__init__.py
+++ b/lib/hawat/blueprints/users/__init__.py
@@ -31,19 +31,430 @@ __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea
 
 import flask
 import flask_login
-from flask_babel import lazy_gettext
+import flask_principal
+import flask_mail
+from flask_babel import gettext, lazy_gettext, force_locale
+from sqlalchemy import or_
 
-import vial.blueprints.users
-from vial.blueprints.users import BLUEPRINT_NAME, ListView, ShowView, MeView,\
-    EnableView, DisableView, DeleteView, AddMembershipView, RejectMembershipView, RemoveMembershipView, AddManagementView, RemoveManagementView
-from hawat.blueprints.users.forms import CreateUserAccountForm, UpdateUserAccountForm,\
-    AdminUpdateUserAccountForm
+import hawat.const
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import ItemListView, ItemShowView, ItemCreateView, ItemUpdateView, ItemDeleteView, ItemEnableView, ItemDisableView, ItemObjectRelationView
+from hawat.view.mixin import HTMLMixin, SQLAlchemyMixin
+from hawat.blueprints.users.forms import CreateUserAccountForm, UpdateUserAccountForm, AdminUpdateUserAccountForm, UserSearchForm
 
 
-class CreateView(vial.blueprints.users.CreateView):  # pylint: disable=locally-disabled,too-many-ancestors
+BLUEPRINT_NAME = 'users'
+"""Name of the blueprint as module global constant."""
+
+
+class ListView(HTMLMixin, SQLAlchemyMixin, ItemListView):
+    """
+    General user account listing.
+    """
+    methods = ['GET']
+
+    authentication = True
+
+    authorization = [hawat.acl.PERMISSION_POWER]
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return lazy_gettext('User management')
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @classmethod
+    def get_action_menu(cls):
+        action_menu = hawat.menu.Menu()
+        action_menu.add_entry(
+            'endpoint',
+            'create',
+            endpoint = 'users.create',
+            resptitle = True
+        )
+        return action_menu
+
+    @classmethod
+    def get_context_action_menu(cls):
+        action_menu = hawat.menu.Menu()
+        action_menu.add_entry(
+            'endpoint',
+            'show',
+            endpoint = 'users.show',
+            hidetitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'update',
+            endpoint = 'users.update',
+            hidetitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'disable',
+            endpoint = 'users.disable',
+            hidetitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'enable',
+            endpoint = 'users.enable',
+            hidetitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'delete',
+            endpoint = 'users.delete',
+            hidetitle = True
+        )
+        return action_menu
+
+    @staticmethod
+    def get_search_form(request_args):
+        """
+        Must return instance of :py:mod:`flask_wtf.FlaskForm` appropriate for
+        searching given type of items.
+        """
+        roles = list(zip(flask.current_app.config['ROLES'], flask.current_app.config['ROLES']))
+        return UserSearchForm(
+            request_args,
+            meta = {'csrf': False},
+            choices_roles = roles
+        )
+
+    @staticmethod
+    def build_query(query, model, form_args):
+        # Adjust query based on text search string.
+        if 'search' in form_args and form_args['search']:
+            query = query\
+                .filter(
+                    or_(
+                        model.login.like('%{}%'.format(form_args['search'])),
+                        model.fullname.like('%{}%'.format(form_args['search'])),
+                        model.email.like('%{}%'.format(form_args['search'])),
+                    )
+                )
+        # Adjust query based on lower time boudary selection.
+        if 'dt_from' in form_args and form_args['dt_from']:
+            query = query.filter(model.createtime >= form_args['dt_from'])
+        # Adjust query based on upper time boudary selection.
+        if 'dt_to' in form_args and form_args['dt_to']:
+            query = query.filter(model.createtime <= form_args['dt_to'])
+        # Adjust query based on user state selection.
+        if 'state' in form_args and form_args['state']:
+            if form_args['state'] == 'enabled':
+                query = query.filter(model.enabled == True)
+            elif form_args['state'] == 'disabled':
+                query = query.filter(model.enabled == False)
+        # Adjust query based on user role selection.
+        if 'role' in form_args and form_args['role']:
+            if form_args['role'] == hawat.const.NO_ROLE:
+                query = query.filter(model.roles == [])
+            else:
+                query = query.filter(model.roles.any(form_args['role']))
+        # Adjust query based on user membership selection.
+        if 'membership' in form_args and form_args['membership']:
+            query = query\
+                .join(model.memberships)\
+                .filter(model.memberships.any(id = form_args['membership'].id))
+        # Adjust query based on user management selection.
+        if 'management' in form_args and form_args['management']:
+            query = query\
+                .join(model.managements)\
+                .filter(model.managements.any(id = form_args['management'].id))
+        if 'sortby' in form_args and form_args['sortby']:
+            sortmap = {
+                'createtime.desc': lambda x, y: x.order_by(y.createtime.desc()),
+                'createtime.asc': lambda x, y: x.order_by(y.createtime.asc()),
+                'login.desc': lambda x, y: x.order_by(y.login.desc()),
+                'login.asc': lambda x, y: x.order_by(y.login.asc()),
+                'fullname.desc': lambda x, y: x.order_by(y.fullname.desc()),
+                'fullname.asc': lambda x, y: x.order_by(y.fullname.asc()),
+                'email.desc': lambda x, y: x.order_by(y.email.desc()),
+                'email.asc': lambda x, y: x.order_by(y.email.asc()),
+                'logintime.desc': lambda x, y: x.order_by(y.logintime.desc()),
+                'logintime.asc': lambda x, y: x.order_by(y.logintime.asc()),
+            }
+            query = sortmap[form_args['sortby']](query, model)
+        return query
+
+
+class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView):
+    """
+    Detailed user account view.
+    """
+    methods = ['GET']
+
+    authentication = True
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-show-user'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Show details of user account &quot;%(item)s&quot;',
+            item = flask.escape(kwargs['item'].login)
+        )
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return lazy_gettext('Show user account details')
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        # Each user must be able to view his/her account.
+        permission_me = flask_principal.Permission(
+            flask_principal.UserNeed(kwargs['item'].id)
+        )
+        # Managers of the groups the user is member of may view his/her account.
+        needs = [hawat.acl.ManagementNeed(x.id) for x in kwargs['item'].memberships]
+        permission_mngr = flask_principal.Permission(*needs)
+        return hawat.acl.PERMISSION_POWER.can() or permission_mngr.can() or permission_me.can()
+
+    @classmethod
+    def get_action_menu(cls):
+        action_menu = hawat.menu.Menu()
+        action_menu.add_entry(
+            'endpoint',
+            'update',
+            endpoint = 'users.update',
+            resptitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'disable',
+            endpoint = 'users.disable',
+            resptitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'enable',
+            endpoint = 'users.enable',
+            resptitle = True
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'delete',
+            endpoint = 'users.delete',
+            resptitle = True
+        )
+        return action_menu
+
+    def do_before_response(self, **kwargs):  # pylint: disable=locally-disabled,no-self-use,unused-argument
+        item = self.response_context['item']
+        action_menu = hawat.menu.Menu()
+        action_menu.add_entry(
+            'endpoint',
+            'show',
+            endpoint = 'groups.show',
+            hidetitle = True
+        )
+        action_menu.add_entry(
+            'submenu',
+            'more',
+            align_right = True,
+            legend = gettext('More actions')
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'more.add_member',
+            endpoint = 'groups.addmember'
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'more.reject_member',
+            endpoint = 'groups.rejectmember'
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'more.remove_member',
+            endpoint = 'groups.removemember'
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'more.add_manager',
+            endpoint = 'groups.addmanager'
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'more.remove_manager',
+            endpoint = 'groups.removemanager'
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'more.enable',
+            endpoint = 'groups.enable'
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'more.disable',
+            endpoint = 'groups.disable'
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'more.update',
+            endpoint = 'groups.update'
+        )
+        self.response_context.update(
+            context_action_menu_groups = action_menu
+        )
+
+        if self.has_endpoint('changelogs.search'):
+            self.response_context.update(
+                context_action_menu_changelogs = self.get_endpoint_class(
+                    'changelogs.search'
+                ).get_context_action_menu()
+            )
+
+            if self.can_access_endpoint('users.update', item = item) and self.has_endpoint('changelogs.search'):
+                item_changelog_model = self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+                item_changelog = self.dbsession.query(item_changelog_model).\
+                    filter(item_changelog_model.model == item.__class__.__name__).\
+                    filter(item_changelog_model.model_id == item.id).\
+                    order_by(item_changelog_model.createtime.desc()).\
+                    limit(100).\
+                    all()
+                self.response_context.update(
+                    item_changelog = item_changelog
+                )
+
+                user_changelog = self.dbsession.query(item_changelog_model).\
+                    filter(item_changelog_model.author_id == item.id).\
+                    order_by(item_changelog_model.createtime.desc()).\
+                    limit(100).\
+                    all()
+                self.response_context.update(
+                    user_changelog = user_changelog
+                )
+
+
+class MeView(ShowView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    Detailed user account view for currently logged-in user (profile page).
+    """
+    methods = ['GET']
+
+    authentication = True
+
+    authorization = [hawat.acl.PERMISSION_ANY]
+
+    @classmethod
+    def get_view_name(cls):
+        return 'me'
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'profile'
+
+    @classmethod
+    def get_menu_title(cls, **kwargs):
+        return lazy_gettext('My account')
+
+    @classmethod
+    def get_view_url(cls, **kwargs):
+        return flask.url_for(cls.get_view_endpoint())
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return lazy_gettext('My user account')
+
+    @classmethod
+    def get_view_template(cls):
+        return '{}/show.html'.format(BLUEPRINT_NAME)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        return True
+
+    @classmethod
+    def get_breadcrumbs_menu(cls):  # pylint: disable=locally-disabled,unused-argument
+        action_menu = hawat.menu.Menu()
+        action_menu.add_entry(
+            'endpoint',
+            'home',
+            endpoint = flask.current_app.config['ENDPOINT_HOME']
+        )
+        action_menu.add_entry(
+            'endpoint',
+            'show',
+            endpoint = '{}.me'.format(cls.module_name)
+        )
+        return action_menu
+
+    def dispatch_request(self):  # pylint: disable=locally-disabled,arguments-differ
+        """
+        Mandatory interface required by the :py:func:`flask.views.View.dispatch_request`.
+        Will be called by the *Flask* framework to service the request.
+
+        Single item with given unique identifier will be retrieved from database
+        and injected into template to be displayed to the user.
+        """
+        item_id = flask_login.current_user.get_id()
+        item = self.dbquery().filter(self.dbmodel.id == item_id).first()
+        if not item:
+            self.abort(404)
+
+        self.response_context.update(
+            item_id = item_id,
+            item = item,
+            breadcrumbs_menu = self.get_breadcrumbs_menu(),
+            action_menu = self.get_action_menu()
+        )
+
+        self.do_before_response()
+
+        return self.generate_response()
+
+
+class CreateView(HTMLMixin, SQLAlchemyMixin, ItemCreateView):  # pylint: disable=locally-disabled,too-many-ancestors
     """
     View for creating new user accounts.
     """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    authorization = [hawat.acl.PERMISSION_POWER]
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-create-user'
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return lazy_gettext('Create new user account')
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User account <strong>%(item_id)s</strong> was successfully created.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext('Unable to create new user account.')
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext('Canceled creating new user account.')
 
     @staticmethod
     def get_item_form(item):
@@ -61,10 +472,64 @@ class CreateView(vial.blueprints.users.CreateView):  # pylint: disable=locally-d
         )
 
 
-class UpdateView(vial.blueprints.users.UpdateView):  # pylint: disable=locally-disabled,too-many-ancestors
+class UpdateView(HTMLMixin, SQLAlchemyMixin, ItemUpdateView):  # pylint: disable=locally-disabled,too-many-ancestors
     """
     View for updating existing user accounts.
     """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-update-user'
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return lazy_gettext('Update user account details')
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Update details of user account &quot;%(item)s&quot;',
+            item = flask.escape(kwargs['item'].login)
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        permission_me = flask_principal.Permission(
+            flask_principal.UserNeed(kwargs['item'].id)
+        )
+        return hawat.acl.PERMISSION_ADMIN.can() or permission_me.can()
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User account <strong>%(item_id)s</strong> was successfully updated.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to update user account <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled updating user account <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
 
     @staticmethod
     def get_item_form(item):
@@ -93,12 +558,632 @@ class UpdateView(vial.blueprints.users.UpdateView):  # pylint: disable=locally-d
         return form
 
 
+class AddMembershipView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for adding group memberships.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    @classmethod
+    def get_view_name(cls):
+        return 'addmembership'
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return gettext('Add group membership')
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-add-member'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Add user &quot;%(user_id)s&quot; to group &quot;%(group_id)s&quot;',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def dbmodel_other(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        permission_m = flask_principal.Permission(
+            hawat.acl.ManagementNeed(kwargs['other'].id)
+        )
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
+
+    @classmethod
+    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        # Reject item change in case given item is already enabled.
+        if kwargs['other'] in kwargs['item'].memberships:
+            return False
+        return True
+
+    @classmethod
+    def change_item(cls, **kwargs):
+        kwargs['item'].memberships.append(kwargs['other'])
+        try:
+            kwargs['item'].memberships_wanted.remove(kwargs['other'])
+        except ValueError:
+            pass
+        if kwargs['item'].is_state_disabled():
+            kwargs['item'].set_state_enabled()
+            flask.current_app.send_infomail(
+                'users.enable',
+                account = kwargs['item']
+            )
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User <strong>%(user_id)s</strong> was successfully added as a member to group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to add user <strong>%(user_id)s</strong> as a member to group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled adding user <strong>%(user_id)s</strong> as a member to group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+
+class RejectMembershipView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for rejecting group membership requests.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    @classmethod
+    def get_view_name(cls):
+        return 'rejectmembership'
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return gettext('Reject group membership')
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-rej-member'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Reject user`s &quot;%(user_id)s&quot; membership request for group &quot;%(group_id)s&quot;',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def dbmodel_other(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        permission_m = flask_principal.Permission(
+            hawat.acl.ManagementNeed(kwargs['other'].id)
+        )
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
+
+    @classmethod
+    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        # Reject item change in case given item is already enabled.
+        if kwargs['other'] not in kwargs['item'].memberships_wanted:
+            return False
+        return True
+
+    @classmethod
+    def change_item(cls, **kwargs):
+        kwargs['item'].memberships_wanted.remove(kwargs['other'])
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User`s <strong>%(user_id)s</strong> membership request for group <strong>%(group_id)s</strong> was successfully rejected.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to reject user`s <strong>%(user_id)s</strong> membership request for group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled rejecting user`s <strong>%(user_id)s</strong> membership request for group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+
+class RemoveMembershipView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for removing group memberships.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    @classmethod
+    def get_view_name(cls):
+        return 'removemembership'
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return gettext('Remove group membership')
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-rem-member'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Remove user &quot;%(user_id)s&quot; from group &quot;%(group_id)s&quot;',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def dbmodel_other(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        permission_m = flask_principal.Permission(
+            hawat.acl.ManagementNeed(kwargs['other'].id)
+        )
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
+
+    @classmethod
+    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        # Reject item change in case given item is already enabled.
+        if kwargs['other'] not in kwargs['item'].memberships:
+            return False
+        return True
+
+    @classmethod
+    def change_item(cls, **kwargs):
+        try:
+            kwargs['item'].memberships.remove(kwargs['other'])
+        except ValueError:
+            pass
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User <strong>%(user_id)s</strong> was successfully removed as a member from group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to remove user <strong>%(user_id)s</strong> as a member from group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled removing user <strong>%(user_id)s</strong> as a member from group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+
+class AddManagementView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for adding group managements.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    @classmethod
+    def get_view_name(cls):
+        return 'addmanagement'
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return gettext('Add group management')
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-add-manager'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Add user &quot;%(user_id)s&quot; to group &quot;%(group_id)s&quot; as manager',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def dbmodel_other(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        permission_m = flask_principal.Permission(
+            hawat.acl.ManagementNeed(kwargs['other'].id)
+        )
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
+
+    @classmethod
+    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        # Reject item change in case given item is already manager.
+        if kwargs['other'] in kwargs['item'].managements:
+            return False
+        return True
+
+    @classmethod
+    def change_item(cls, **kwargs):
+        kwargs['item'].managements.append(kwargs['other'])
+        if kwargs['item'].is_state_disabled():
+            kwargs['item'].set_state_enabled()
+            flask.current_app.send_infomail(
+                'users.enable',
+                account = kwargs['item']
+            )
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User <strong>%(user_id)s</strong> was successfully added as a manager to group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to add user <strong>%(user_id)s</strong> as a manager to group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled adding user <strong>%(user_id)s</strong> as a manager to group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+
+class RemoveManagementView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for removing group managements.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    @classmethod
+    def get_view_name(cls):
+        return 'removemanagement'
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return gettext('Remove group management')
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-rem-manager'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Remove user &quot;%(user_id)s&quot; from group &quot;%(group_id)s&quot; as manager',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def dbmodel_other(self):
+        return self.get_model(hawat.const.MODEL_GROUP)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @classmethod
+    def authorize_item_action(cls, **kwargs):
+        permission_m = flask_principal.Permission(
+            hawat.acl.ManagementNeed(kwargs['other'].id)
+        )
+        return hawat.acl.PERMISSION_POWER.can() or permission_m.can()
+
+    @classmethod
+    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        # Reject item change in case given item is not already manager.
+        if kwargs['other'] not in kwargs['item'].managements:
+            return False
+        return True
+
+    @classmethod
+    def change_item(cls, **kwargs):
+        try:
+            kwargs['item'].managements.remove(kwargs['other'])
+        except ValueError:
+            pass
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User <strong>%(user_id)s</strong> was successfully removed as a manager from group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to remove user <strong>%(user_id)s</strong> as a manager from group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled removing user <strong>%(user_id)s</strong> as a manager from group <strong>%(group_id)s</strong>.',
+            user_id  = flask.escape(str(kwargs['item'])),
+            group_id = flask.escape(str(kwargs['other']))
+        )
+
+
+class EnableView(HTMLMixin, SQLAlchemyMixin, ItemEnableView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for enabling existing user accounts.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    authorization = [hawat.acl.PERMISSION_POWER]
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-enable-user'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Enable user account &quot;%(item)s&quot;',
+            item = flask.escape(kwargs['item'].login)
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User account <strong>%(item_id)s</strong> was successfully enabled.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to enable user account <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled enabling user account <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @classmethod
+    def inform_user(cls, account):
+        """Send infomail about user account activation."""
+        mail_locale = account.locale
+        if not mail_locale:
+            mail_locale = flask.current_app.config['BABEL_DEFAULT_LOCALE']
+
+        with force_locale(mail_locale):
+            msg = flask_mail.Message(
+                gettext(
+                    "[%(app_name)s] Account activation - %(item_id)s",
+                    app_name = flask.current_app.config['APPLICATION_NAME'],
+                    item_id = account.login
+                ),
+                recipients = [account.email],
+                bcc = flask.current_app.config['EMAIL_ADMINS']
+            )
+            msg.body = flask.render_template(
+                'users/email_activation.txt',
+                account = account
+            )
+            flask.current_app.mailer.send(msg)
+
+    def do_after_action(self, item):
+        self.inform_user(item)
+
+
+class DisableView(HTMLMixin, SQLAlchemyMixin, ItemDisableView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for disabling user accounts.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    authorization = [hawat.acl.PERMISSION_POWER]
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-disable-user'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Disable user account &quot;%(item)s&quot;',
+            item = flask.escape(kwargs['item'].login)
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User account <strong>%(item_id)s</strong> was successfully disabled.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to disable user account <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled disabling user account <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+
+class DeleteView(HTMLMixin, SQLAlchemyMixin, ItemDeleteView):  # pylint: disable=locally-disabled,too-many-ancestors
+    """
+    View for deleting existing user accounts.
+    """
+    methods = ['GET','POST']
+
+    authentication = True
+
+    authorization = [hawat.acl.PERMISSION_ADMIN]
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'action-delete-user'
+
+    @classmethod
+    def get_menu_legend(cls, **kwargs):
+        return lazy_gettext(
+            'Delete user account &quot;%(item)s&quot;',
+            item = flask.escape(kwargs['item'].login)
+        )
+
+    @property
+    def dbmodel(self):
+        return self.get_model(hawat.const.MODEL_USER)
+
+    @property
+    def dbchlogmodel(self):
+        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User account <strong>%(item_id)s</strong> was successfully and permanently deleted.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext(
+            'Unable to delete user account <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext(
+            'Canceled deleting user account <strong>%(item_id)s</strong>.',
+            item_id = flask.escape(str(kwargs['item']))
+        )
+
+
 #-------------------------------------------------------------------------------
 
 
-class UsersBlueprint(vial.blueprints.users.UsersBlueprint):
+class UsersBlueprint(HawatBlueprint):
     """Pluggable module - user account management (*users*)."""
 
+    @classmethod
+    def get_module_title(cls):
+        return lazy_gettext('User account management')
+
     def register_app(self, app):
         app.menu_main.add_entry(
             'view',
@@ -123,8 +1208,8 @@ class UsersBlueprint(vial.blueprints.users.UsersBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/users/forms.py b/lib/hawat/blueprints/users/forms.py
index 60fe0a317..4944ac34d 100644
--- a/lib/hawat/blueprints/users/forms.py
+++ b/lib/hawat/blueprints/users/forms.py
@@ -20,11 +20,13 @@ __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea
 import pytz
 import sqlalchemy
 import wtforms
-from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField
+from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
 from flask_babel import gettext, lazy_gettext
 
-import vial.db
-import vial.forms
+import hawat.db
+import hawat.const
+import hawat.forms
+from hawat.forms import check_login, check_unique_login, get_available_groups
 from mentat.datatype.sqldb import UserModel, GroupModel
 
 
@@ -33,7 +35,7 @@ def check_id_existence(form, field):
     Callback for validating user logins during account create action.
     """
     try:
-        vial.db.db_get().session.query(UserModel).\
+        hawat.db.db_get().session.query(UserModel).\
             filter(UserModel.login == field.data).\
             one()
     except sqlalchemy.orm.exc.NoResultFound:
@@ -47,7 +49,7 @@ def check_id_uniqueness(form, field):
     """
     Callback for validating user logins during account update action.
     """
-    user = vial.db.db_get().session.query(UserModel).\
+    user = hawat.db.db_get().session.query(UserModel).\
         filter(UserModel.login == field.data).\
         filter(UserModel.id != form.db_item_id).\
         all()
@@ -60,10 +62,10 @@ def get_available_groups():
     """
     Query the database for list of all available groups.
     """
-    return vial.db.db_query(GroupModel).order_by(GroupModel.name).all()
+    return hawat.db.db_query(GroupModel).order_by(GroupModel.name).all()
 
 
-class BaseUserAccountForm(vial.forms.BaseItemForm):
+class BaseUserAccountForm(hawat.forms.BaseItemForm):
     """
     Class representing base user account form.
     """
@@ -89,7 +91,7 @@ class BaseUserAccountForm(vial.forms.BaseItemForm):
             wtforms.validators.Length(min = 3, max = 250)
         ]
     )
-    locale = vial.forms.SelectFieldWithNone(
+    locale = hawat.forms.SelectFieldWithNone(
         lazy_gettext('Prefered locale:'),
         validators = [
             wtforms.validators.Optional()
@@ -98,7 +100,7 @@ class BaseUserAccountForm(vial.forms.BaseItemForm):
         filters = [lambda x: x or None],
         default = ''
     )
-    timezone = vial.forms.SelectFieldWithNone(
+    timezone = hawat.forms.SelectFieldWithNone(
         lazy_gettext('Prefered timezone:'),
         validators = [
             wtforms.validators.Optional(),
@@ -141,8 +143,8 @@ class AdminUserAccountForm(BaseUserAccountForm):
             (True,  lazy_gettext('Enabled')),
             (False, lazy_gettext('Disabled'))
         ],
-        filters = [vial.forms.str_to_bool],
-        coerce = vial.forms.str_to_bool
+        filters = [hawat.forms.str_to_bool],
+        coerce = hawat.forms.str_to_bool
     )
     roles = wtforms.SelectMultipleField(
         lazy_gettext('Roles:'),
@@ -184,7 +186,8 @@ class CreateUserAccountForm(AdminUserAccountForm):
         validators = [
             wtforms.validators.DataRequired(),
             wtforms.validators.Length(min = 3, max = 50),
-            vial.forms.check_login,
+            check_login,
+            check_unique_login,
             check_id_existence
         ]
     )
@@ -205,7 +208,8 @@ class AdminUpdateUserAccountForm(AdminUserAccountForm):
         validators = [
             wtforms.validators.DataRequired(),
             wtforms.validators.Length(min = 3, max = 50),
-            vial.forms.check_login,
+            hawat.forms.check_login,
+            check_unique_login,
             check_id_uniqueness
         ]
     )
@@ -220,3 +224,114 @@ class AdminUpdateUserAccountForm(AdminUserAccountForm):
         # Store the ID of original item in database to enable the ID uniqueness
         # check with check_id_uniqueness() validator.
         self.db_item_id = kwargs['db_item_id']
+
+
+class UserSearchForm(hawat.forms.BaseSearchForm):
+    """
+    Class representing simple user search form.
+    """
+    search = wtforms.StringField(
+        lazy_gettext('Login, name, email:'),
+        validators = [
+            wtforms.validators.Optional(),
+            wtforms.validators.Length(min = 3, max = 100)
+        ],
+        description = lazy_gettext('User`s login, full name or email address. Search is performed even in the middle of the strings, so for example you may lookup by domain.')
+    )
+    dt_from = hawat.forms.SmartDateTimeField(
+        lazy_gettext('Creation time from:'),
+        validators = [
+            wtforms.validators.Optional()
+        ],
+        description = lazy_gettext('Lower time boundary for item creation time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences.')
+    )
+    dt_to = hawat.forms.SmartDateTimeField(
+        lazy_gettext('Creation time to:'),
+        validators = [
+            wtforms.validators.Optional()
+        ],
+        description = lazy_gettext('Upper time boundary for item creation time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences.')
+    )
+
+    state = wtforms.SelectField(
+        lazy_gettext('State:'),
+        validators = [
+            wtforms.validators.Optional(),
+        ],
+        choices = [
+            ('', lazy_gettext('Nothing selected')),
+            ('enabled',  lazy_gettext('Enabled')),
+            ('disabled', lazy_gettext('Disabled'))
+        ],
+        default = '',
+        description = lazy_gettext('Search for users with particular account state.')
+    )
+    role = wtforms.SelectField(
+        lazy_gettext('Role:'),
+        validators = [
+            wtforms.validators.Optional()
+        ],
+        default = '',
+        description = lazy_gettext('Search for users with particular role, or without any assigned roles.')
+    )
+    membership = QuerySelectField(
+        lazy_gettext('Group membership:'),
+        query_factory = get_available_groups,
+        allow_blank = True,
+        description = lazy_gettext('Search for users with membership with particular group.')
+    )
+    management = QuerySelectField(
+        lazy_gettext('Group management:'),
+        query_factory = get_available_groups,
+        allow_blank = True,
+        description = lazy_gettext('Search for users with management rights to particular group.')
+    )
+
+    sortby = wtforms.SelectField(
+        lazy_gettext('Sort result by:'),
+        validators = [
+            wtforms.validators.Optional()
+        ],
+        choices = [
+            ('createtime.desc', lazy_gettext('by creation time descending')),
+            ('createtime.asc',  lazy_gettext('by creation time ascending')),
+            ('login.desc', lazy_gettext('by login descending')),
+            ('login.asc',  lazy_gettext('by login ascending')),
+            ('fullname.desc', lazy_gettext('by name descending')),
+            ('fullname.asc',  lazy_gettext('by name ascending')),
+            ('email.desc', lazy_gettext('by email descending')),
+            ('email.asc',  lazy_gettext('by email ascending')),
+            ('logintime.desc', lazy_gettext('by login time descending')),
+            ('logintime.asc',  lazy_gettext('by login time ascending')),
+        ],
+        default = 'fullname.asc'
+    )
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        #
+        # Handle additional custom keywords.
+        #
+
+        # The list of choices for 'roles' attribute comes from outside of the
+        # form to provide as loose tie as possible to the outer application.
+        # Another approach would be to load available choices here with:
+        #
+        #   roles = flask.current_app.config['ROLES']
+        #
+        # That would mean direct dependency on flask.Flask application.
+        self.role.choices = [
+            ('', lazy_gettext('Nothing selected')),
+            (hawat.const.NO_ROLE, lazy_gettext('Without any roles'))
+        ] + kwargs['choices_roles']
+
+    @staticmethod
+    def is_multivalue(field_name):
+        """
+        Check, if given form field is a multivalue field.
+
+        :param str field_name: Name of the form field.
+        :return: ``True``, if the field can contain multiple values, ``False`` otherwise.
+        :rtype: bool
+        """
+        return False
diff --git a/lib/hawat/blueprints/users/test/__init__.py b/lib/hawat/blueprints/users/test/__init__.py
index 1849bddd7..371fa356e 100644
--- a/lib/hawat/blueprints/users/test/__init__.py
+++ b/lib/hawat/blueprints/users/test/__init__.py
@@ -16,17 +16,17 @@ Unit tests for :py:mod:`hawat.blueprints.users`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.test.fixtures
-from vial.test import VialTestCase, ItemCreateVialTestCase
+import hawat.test
+import hawat.test.fixtures
+from hawat.test import HawatTestCase, ItemCreateHawatTestCase
 from hawat.test.runner import TestRunnerMixin
 from hawat.blueprints.users.test.utils import UsersTestCaseMixin
 
 
-class UsersListTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
+class UsersListTestCase(UsersTestCaseMixin, TestRunnerMixin, HawatTestCase):
     """Class for testing ``users.list`` endpoint."""
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """
         Test access as user ``user``.
@@ -35,7 +35,7 @@ class UsersListTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
         """
         self._attempt_fail_list()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """
         Test access as user ``developer``.
@@ -44,7 +44,7 @@ class UsersListTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
         """
         self._attempt_fail_list()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """
         Test access as user ``maintainer``.
@@ -53,7 +53,7 @@ class UsersListTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
         """
         self._attempt_succeed_list()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """
         Test access as user ``admin``.
@@ -63,38 +63,38 @@ class UsersListTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
         self._attempt_succeed_list()
 
 
-class UsersShowOwnTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
+class UsersShowOwnTestCase(UsersTestCaseMixin, TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``users.show`` endpoint: access to user`s own accounts.
 
     Each user must be able to access his own account.
     """
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
         self._attempt_succeed_show(hawat.const.ROLE_USER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
         self._attempt_succeed_show(hawat.const.ROLE_DEVELOPER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
         self._attempt_succeed_show(hawat.const.ROLE_MAINTAINER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
         self._attempt_succeed_show(hawat.const.ROLE_ADMIN)
 
 
-class UsersShowOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
+class UsersShowOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, HawatTestCase):
     """Class for testing ``users.show`` endpoint: access to other user`s accounts."""
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user_developer(self):
         """
         Test access to 'developer' account as user 'user'.
@@ -103,7 +103,7 @@ class UsersShowOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
         """
         self._attempt_fail_show(hawat.const.ROLE_DEVELOPER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user_maintainer(self):
         """
         Test access to 'maintainer' account as user 'user'.
@@ -112,7 +112,7 @@ class UsersShowOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
         """
         self._attempt_fail_show(hawat.const.ROLE_MAINTAINER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_03_as_user_admin(self):
         """
         Test access to 'admin' account as user 'user'.
@@ -123,7 +123,7 @@ class UsersShowOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
 
     #--------------------------------------------------------------------------
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_04_as_developer_user(self):
         """
         Test access to 'user' account as user 'developer'.
@@ -133,7 +133,7 @@ class UsersShowOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
         """
         self._attempt_succeed_show(hawat.const.ROLE_USER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_05_as_developer_maintainer(self):
         """
         Test access to 'maintainer' account as user 'developer'.
@@ -143,7 +143,7 @@ class UsersShowOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
         """
         self._attempt_succeed_show(hawat.const.ROLE_MAINTAINER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_06_as_developer_admin(self):
         """
         Test access to 'admin' account as user 'developer'.
@@ -155,7 +155,7 @@ class UsersShowOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
 
     #--------------------------------------------------------------------------
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_07_as_maintainer_user(self):
         """
         Test access to 'user' account as user 'maintainer'.
@@ -164,7 +164,7 @@ class UsersShowOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
         """
         self._attempt_succeed_show(hawat.const.ROLE_USER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_08_as_maintainer_developer(self):
         """
         Test access to 'developer' account as user 'maintainer'.
@@ -173,7 +173,7 @@ class UsersShowOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
         """
         self._attempt_succeed_show(hawat.const.ROLE_DEVELOPER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_09_as_maintainer_admin(self):
         """
         Test access to 'maintainer' account as user 'maintainer'.
@@ -184,23 +184,23 @@ class UsersShowOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
 
     #--------------------------------------------------------------------------
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_10_as_admin_user(self):
         """Test access to 'user' account as user 'admin'."""
         self._attempt_succeed_show(hawat.const.ROLE_USER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_11_as_admin_developer(self):
         """Test access to 'developer' account as user 'admin'."""
         self._attempt_succeed_show(hawat.const.ROLE_DEVELOPER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_12_as_admin_maintainer(self):
         """Test access to 'maintainer' account as user 'admin'."""
         self._attempt_succeed_show(hawat.const.ROLE_MAINTAINER)
 
 
-class UsersCreateTestCase(UsersTestCaseMixin, TestRunnerMixin, ItemCreateVialTestCase):
+class UsersCreateTestCase(UsersTestCaseMixin, TestRunnerMixin, ItemCreateHawatTestCase):
     """Class for testing ``users.create`` endpoint."""
 
     user_data_fixture = [
@@ -211,125 +211,125 @@ class UsersCreateTestCase(UsersTestCaseMixin, TestRunnerMixin, ItemCreateVialTes
         ('enabled', True)
     ]
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
         self._attempt_fail_create()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
         self._attempt_fail_create()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
         self._attempt_succeed_create(self.user_data_fixture)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
         self._attempt_succeed_create(self.user_data_fixture)
 
 
-class UsersUpdateOwnTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
+class UsersUpdateOwnTestCase(UsersTestCaseMixin, TestRunnerMixin, HawatTestCase):
     """Class for testing ``users.update`` endpoint: access to user`s own accounts."""
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
         self._attempt_succeed_update(hawat.const.ROLE_USER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
         self._attempt_succeed_update(hawat.const.ROLE_DEVELOPER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
         self._attempt_succeed_update(hawat.const.ROLE_MAINTAINER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
         self._attempt_succeed_update(hawat.const.ROLE_ADMIN)
 
 
-class UsersUpdateOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
+class UsersUpdateOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, HawatTestCase):
     """Class for testing ``users.update`` endpoint: access to other user`s accounts."""
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user_developer(self):
         """Test access to 'developer' account as user 'user'."""
         self._attempt_fail_update(hawat.const.ROLE_DEVELOPER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user_maintainer(self):
         """Test access to 'maintainer' account as user 'user'."""
         self._attempt_fail_update(hawat.const.ROLE_MAINTAINER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_03_as_user_admin(self):
         """Test access to 'admin' account as user 'user'."""
         self._attempt_fail_update(hawat.const.ROLE_ADMIN)
 
     #--------------------------------------------------------------------------
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_04_as_developer_user(self):
         """Test access to 'user' account as user 'developer'."""
         self._attempt_fail_update(hawat.const.ROLE_USER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_05_as_developer_maintainer(self):
         """Test access to 'maintainer' account as user 'developer'."""
         self._attempt_fail_update(hawat.const.ROLE_MAINTAINER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_06_as_developer_admin(self):
         """Test access to 'admin' account as user 'developer'."""
         self._attempt_fail_update(hawat.const.ROLE_ADMIN)
 
     #--------------------------------------------------------------------------
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_07_as_maintainer_user(self):
         """Test access to 'user' account as user 'maintainer'."""
         self._attempt_fail_update(hawat.const.ROLE_USER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_08_as_maintainer_developer(self):
         """Test access to 'developer' account as user 'maintainer'."""
         self._attempt_fail_update(hawat.const.ROLE_DEVELOPER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_09_as_maintainer_admin(self):
         """Test access to 'admin' account as user 'maintainer'."""
         self._attempt_fail_update(hawat.const.ROLE_ADMIN)
 
     #--------------------------------------------------------------------------
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_10_as_admin_user(self):
         """Test access to 'user' account as user 'admin'."""
         self._attempt_succeed_update(hawat.const.ROLE_USER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_11_as_admin_developer(self):
         """Test access to 'developer' account as user 'admin'."""
         self._attempt_succeed_update(hawat.const.ROLE_DEVELOPER)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_12_as_admin_maintainer(self):
         """Test access to 'maintainer' account as user 'admin'."""
         self._attempt_succeed_update(hawat.const.ROLE_MAINTAINER)
 
 
-class UsersEnableDisableTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
+class UsersEnableDisableTestCase(UsersTestCaseMixin, TestRunnerMixin, HawatTestCase):
     """Class for testing ``users.enable`` and ``users.disable`` endpoint."""
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
         for uname in (
@@ -341,7 +341,7 @@ class UsersEnableDisableTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCa
             self._attempt_fail_disable(uname)
             self._attempt_fail_enable(uname)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
         for uname in (
@@ -353,7 +353,7 @@ class UsersEnableDisableTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCa
             self._attempt_fail_disable(uname)
             self._attempt_fail_enable(uname)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
         for uname in (
@@ -364,7 +364,7 @@ class UsersEnableDisableTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCa
             self._attempt_succeed_disable(uname)
             self._attempt_succeed_enable(uname)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
         for uname in (
@@ -376,7 +376,7 @@ class UsersEnableDisableTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCa
             self._attempt_succeed_enable(uname)
 
 
-class UsersAddRemRejMembershipTestCase(TestRunnerMixin, VialTestCase):
+class UsersAddRemRejMembershipTestCase(TestRunnerMixin, HawatTestCase):
     """Class for testing ``users.add_membership``, ``users.reject_membership`` and ``users.remove_membership`` endpoint."""
 
     def _attempt_fail(self, uname, gname):
@@ -501,7 +501,7 @@ class UsersAddRemRejMembershipTestCase(TestRunnerMixin, VialTestCase):
         )
         self.mailbox_monitoring('off')
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
         for uname in (
@@ -510,9 +510,9 @@ class UsersAddRemRejMembershipTestCase(TestRunnerMixin, VialTestCase):
                 hawat.const.ROLE_MAINTAINER,
                 hawat.const.ROLE_ADMIN
             ):
-            self._attempt_fail(uname, vial.test.fixtures.DEMO_GROUP_A)
+            self._attempt_fail(uname, hawat.test.fixtures.DEMO_GROUP_A)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
         for uname in (
@@ -520,9 +520,9 @@ class UsersAddRemRejMembershipTestCase(TestRunnerMixin, VialTestCase):
                 hawat.const.ROLE_MAINTAINER,
                 hawat.const.ROLE_ADMIN
             ):
-            self._attempt_succeed(uname, vial.test.fixtures.DEMO_GROUP_A)
+            self._attempt_succeed(uname, hawat.test.fixtures.DEMO_GROUP_A)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
         for uname in (
@@ -530,9 +530,9 @@ class UsersAddRemRejMembershipTestCase(TestRunnerMixin, VialTestCase):
                 hawat.const.ROLE_DEVELOPER,
                 hawat.const.ROLE_ADMIN
             ):
-            self._attempt_succeed(uname, vial.test.fixtures.DEMO_GROUP_A)
+            self._attempt_succeed(uname, hawat.test.fixtures.DEMO_GROUP_A)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
         for uname in (
@@ -540,13 +540,13 @@ class UsersAddRemRejMembershipTestCase(TestRunnerMixin, VialTestCase):
                 hawat.const.ROLE_DEVELOPER,
                 hawat.const.ROLE_MAINTAINER
             ):
-            self._attempt_succeed(uname, vial.test.fixtures.DEMO_GROUP_A)
+            self._attempt_succeed(uname, hawat.test.fixtures.DEMO_GROUP_A)
 
 
-class UsersDeleteTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
+class UsersDeleteTestCase(UsersTestCaseMixin, TestRunnerMixin, HawatTestCase):
     """Class for testing ``users.delete`` endpoint."""
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_01_as_user(self):
         """Test access as user 'user'."""
         for uname in (
@@ -557,7 +557,7 @@ class UsersDeleteTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
             ):
             self._attempt_fail_delete(uname)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_02_as_developer(self):
         """Test access as user 'developer'."""
         for uname in (
@@ -568,7 +568,7 @@ class UsersDeleteTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
             ):
             self._attempt_fail_delete(uname)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_03_as_maintainer(self):
         """Test access as user 'maintainer'."""
         for uname in (
@@ -579,7 +579,7 @@ class UsersDeleteTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
             ):
             self._attempt_fail_delete(uname)
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_04_as_admin(self):
         """Test access as user 'admin'."""
         for uname in (
diff --git a/lib/hawat/blueprints/whois/__init__.py b/lib/hawat/blueprints/whois/__init__.py
index 975338cda..04b027200 100644
--- a/lib/hawat/blueprints/whois/__init__.py
+++ b/lib/hawat/blueprints/whois/__init__.py
@@ -59,13 +59,13 @@ import mentat.services.whois
 from mentat.const import tr_
 
 import hawat.const
-import vial.db
+import hawat.db
 import hawat.const
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import RenderableView
-from vial.view.mixin import HTMLMixin, AJAXMixin, SnippetMixin
-from vial.utils import URLParamsBuilder
+import hawat.acl
+from hawat.app import HawatBlueprint
+from hawat.view import RenderableView
+from hawat.view.mixin import HTMLMixin, AJAXMixin, SnippetMixin
+from hawat.utils import URLParamsBuilder
 from hawat.blueprints.whois.forms import WhoisSearchForm
 
 
@@ -167,7 +167,7 @@ class SnippetSearchView(SnippetMixin, AbstractSearchView):  # pylint: disable=lo
 #-------------------------------------------------------------------------------
 
 
-class WhoisBlueprint(VialBlueprint):
+class WhoisBlueprint(HawatBlueprint):
     """Pluggable module - Local WHOIS service (*whois*)."""
 
     @classmethod
@@ -208,8 +208,8 @@ class WhoisBlueprint(VialBlueprint):
 
 def get_blueprint():
     """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
+    Mandatory interface for :py:mod:`hawat.Hawat` and factory function. This function
+    must return a valid instance of :py:class:`hawat.app.HawatBlueprint` or
     :py:class:`flask.Blueprint`.
     """
 
diff --git a/lib/hawat/blueprints/whois/test/__init__.py b/lib/hawat/blueprints/whois/test/__init__.py
index 53c0df4a9..8f27bb0b5 100644
--- a/lib/hawat/blueprints/whois/test/__init__.py
+++ b/lib/hawat/blueprints/whois/test/__init__.py
@@ -16,13 +16,13 @@ Unit tests for :py:mod:`hawat.blueprints.whois`.
 import unittest
 
 import hawat.const
-import vial.test
-import vial.db
-from vial.test import VialTestCase
+import hawat.test
+import hawat.db
+from hawat.test import HawatTestCase
 from hawat.test.runner import TestRunnerMixin
 
 
-class SearchTestCase(TestRunnerMixin, VialTestCase):
+class SearchTestCase(TestRunnerMixin, HawatTestCase):
     """
     Class for testing ``whois.search`` endpoint.
     """
@@ -51,22 +51,22 @@ class SearchTestCase(TestRunnerMixin, VialTestCase):
         """Test access as anonymous user."""
         self._attempt_fail()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_USER)
     def test_02_as_user(self):
         """Test access as user ``user``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
     def test_03_as_developer(self):
         """Test access as user ``developer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
     def test_04_as_maintainer(self):
         """Test access as user ``maintainer``."""
         self._attempt_succeed()
 
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
+    @hawat.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
     def test_05_as_admin(self):
         """Test access as user ``admin``."""
         self._attempt_succeed()
diff --git a/lib/hawat/test/__init__.py b/lib/hawat/test/__init__.py
index 7998eb711..bb036ef4b 100644
--- a/lib/hawat/test/__init__.py
+++ b/lib/hawat/test/__init__.py
@@ -1,20 +1,455 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 #-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
 # Use of this source is governed by the MIT license, see LICENSE file.
 #-------------------------------------------------------------------------------
 
 
 """
-Base library for Hawat unit tests.
+Base library for web interface unit tests.
 """
 
-from vial.test import RegistrationVialTestCase
 
-class RegistrationHawatTestCase(RegistrationVialTestCase):
+import io
+import unittest
+import pprint
+import logging
+import flask_mail
+import vial
+import vial.app
+import vial.db
+
+
+class do_as_user_decorator:  # pylint: disable=locally-disabled,invalid-name,too-few-public-methods
+    """
+    Decorator class for accessing application endpoints as given user.
+    """
+    def __init__(self, user_name, login_func_name = 'login_dev', login_func_params = None):
+        self.user_name         = user_name
+        self.login_func_name   = login_func_name
+        self.login_func_params = login_func_params or {}
+
+    def __call__(self, func):
+        def wrapped_f(other_self, *args, **kwargs):
+            login_func = getattr(other_self, self.login_func_name)
+            response = login_func(self.user_name, **self.login_func_params)
+            other_self.assertEqual(response.status_code, 200)
+
+            func(other_self, *args, **kwargs)
+
+            response = other_self.logout()
+            other_self.assertEqual(response.status_code, 200)
+
+        return wrapped_f
+
+
+def app_context_wrapper_decorator(func):
+    """
+    Decorator class for conditional wrapping of given function with application context.
+    """
+    def wrapped_f(self, *args, **kwargs):
+        if 'with_app_ctx' not in kwargs or not kwargs['with_app_ctx']:
+            return func(self, *args, **kwargs)
+        with self.app.app_context():
+            return func(self, *args, **kwargs)
+
+    return wrapped_f
+
+
+class VialTestCase(unittest.TestCase):
+    """
+    Class for testing :py:class:`vial.app.Vial` application.
+    """
+
+    logger = logging.getLogger()
+    logger.level = logging.DEBUG
+
+    def on_email_sent(self, message, app):  # pylint: disable=locally-disabled,unused-argument
+        """
+        Signal handler for handling :py:func:`flask_mail.email_dispatched` signal.
+        Log subject and recipients of all emails that have been sent.
+        """
+        #pprint.pprint(message)
+        #app.logger.info(
+        #    "Sent email '%s' to '%s'",
+        #    message.subject,
+        #    ', '.join(message.recipients)
+        #)
+        self.mailbox.append(message)
+
+    #--------------------------------------------------------------------------
+
+    def setUp(self):
+        self.setup_logging()
+        self.mailbox = []
+        self.fixtures_db = []
+
+        self.app = self.setup_app()
+        self.client = self.app.test_client()
+        self.setup_db()
+
+    def setup_logging(self):
+        """
+        Setup logging configuration for testing purposes.
+        """
+        #for hdlr in self.logger.handlers:
+        #    self.logger.removeHandler(hdlr)
+        self.loghdlr = logging.StreamHandler(io.StringIO())
+        self.logger.addHandler(self.loghdlr)
+
+    def setup_app(self):
+        """
+        Setup application object.
+        """
+        raise NotImplementedError()
+
+    def setup_db(self):
+        """
+        Perform database setup.
+        """
+        with self.app.app_context():
+            vial.db.db_get().drop_all()
+            vial.db.db_get().create_all()
+            self.setup_fixtures_db()
+
+    def get_fixtures_db(self, app):
+        raise NotImplementedError()
+
+    def setup_fixtures_db(self):
+        """
+        Setup general database object fixtures.
+        """
+        fixture_list = self.get_fixtures_db(self.app)
+        for dbobject in fixture_list:
+            vial.db.db_session().add(dbobject)
+            vial.db.db_session().commit()
+        self.fixtures_db.extend(fixture_list)
+
+    #--------------------------------------------------------------------------
+
+    def tearDown(self):
+        self.teardown_logging()
+        self.teardown_db()
+
+    def teardown_logging(self):
+        """
+        Teardown logging configuration for testing purposes.
+        """
+        #print(
+        #    "CAPTURED LOG CONTENTS:\n{}".format(
+        #        self.loghdlr.stream.getvalue()
+        #    )
+        #)
+        self.loghdlr.stream.close()
+        self.logger.removeHandler(self.loghdlr)
+
+    def teardown_db(self):
+        with self.app.app_context():
+            vial.db.db_get().drop_all()
+
+    #--------------------------------------------------------------------------
+
+    def login_dev(self, login):
+        """
+        Login given user with *auth_dev* module.
+        """
+        return self.client.post(
+            '/auth_dev/login',
+            data = dict(login = login, submit = 'Login'),
+            follow_redirects = True
+        )
+
+    def login_pwd(self, login, password):
+        """
+        Login given user with *auth_pwd* module.
+        """
+        return self.client.post(
+            '/auth_pwd/login',
+            data = dict(login = login, password = password, submit = 'Login'),
+            follow_redirects = True
+        )
+
+    def login_env(self, login, envvar = 'eppn'):
+        """
+        Login given user with *auth_env* module.
+        """
+        return self.client.get(
+            '/auth_env/login',
+            environ_base = {envvar: login},
+            follow_redirects = True
+        )
+
+    def logout(self):
+        """
+        Logout current user.
+        """
+        return self.client.get(
+            '/logout',
+            follow_redirects = True
+        )
+
+    #--------------------------------------------------------------------------
+
+    def log_get(self):
+        """
+        Get content written to log so far.
+        """
+        return self.loghdlr.stream.getvalue()
+
+    def log_clear(self):
+        """
+        Clear log content.
+        """
+        self.loghdlr.stream.close()
+        self.loghdlr.stream = io.StringIO()
+
+    #--------------------------------------------------------------------------
+
+    def mailbox_clear(self):
+        """
+        Clear internal mailbox.
+        """
+        self.mailbox = []
+
+    def mailbox_monitoring(self, state):
+        """
+        Enable/disable mailbox monitoring.
+        """
+        if state == 'on':
+            flask_mail.email_dispatched.connect(self.on_email_sent)
+            return
+
+        if state == 'off':
+            flask_mail.email_dispatched.disconnect(self.on_email_sent)
+            return
+
+        raise ValueError(
+            'Invalid parameter for mailbox_monitoring, must be "on" or "off", received {}'.format(
+                str(state)
+            )
+        )
+
+    #--------------------------------------------------------------------------
+
+    def user_model(self):
+        """
+        Get user model class.
+        """
+        return self.app.get_model(hawat.const.MODEL_USER)
+
+    @app_context_wrapper_decorator
+    def user_get(self, user_name, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        """
+        Get user object according to given user name from database.
+        """
+        user_model = self.user_model()
+        return vial.db.db_session().query(user_model).filter(user_model.login == user_name).one_or_none()
+
+    @app_context_wrapper_decorator
+    def user_save(self, user_object, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        """
+        Update given user object within database.
+        """
+        vial.db.db_session().add(user_object)
+        vial.db.db_session().commit()
+
+    @app_context_wrapper_decorator
+    def user_id(self, user_name, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        """
+        Get ID of user with given name within database.
+        """
+        uobj = self.user_get(user_name)
+        return uobj.id
+
+    @app_context_wrapper_decorator
+    def user_enabled(self, user_name, state, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        """
+        Enable/disable given user within database.
+        """
+        user = self.user_get(user_name)
+        user.enabled = state
+        self.user_save(user)
+
+    #--------------------------------------------------------------------------
+
+    def group_model(self):
+        """
+        Get user model class.
+        """
+        return self.app.get_model(hawat.const.MODEL_GROUP)
+
+    @app_context_wrapper_decorator
+    def group_get(self, group_name, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        """
+        Get group object according to given group name within database.
+        """
+        group_model = self.group_model()
+        return vial.db.db_session().query(group_model).filter(group_model.name == group_name).one_or_none()
+
+    @app_context_wrapper_decorator
+    def group_save(self, group_object, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        """
+        Update given group object within database.
+        """
+        vial.db.db_session().add(group_object)
+        vial.db.db_session().commit()
+
+    @app_context_wrapper_decorator
+    def group_id(self, group_name, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        """
+        Get ID of given group within database.
+        """
+        gobj = self.group_get(group_name)
+        return gobj.id
+
+    @app_context_wrapper_decorator
+    def group_enabled(self, group_name, state, **kwargs):  # pylint: disable=locally-disabled,unused-argument
+        """
+        Enable/disable given group within database.
+        """
+        group = self.group_get(group_name)
+        group.enabled = state
+        self.group_save(group)
+
+    #--------------------------------------------------------------------------
+
+    def assertGetURL(self, url, status_code = 200, content_checks = None, print_response = False, follow_redirects = True):  # pylint: disable=locally-disabled,invalid-name
+        """
+        Perform GET request and check some default assertions against the response.
+        """
+        response = self.client.get(
+            url,
+            follow_redirects = follow_redirects
+        )
+        if print_response:
+            print("--------------------------------------------------------------------------------")
+            print("Response for GET {}: {} ({})".format(url, response.status_code, response.status))
+            pprint.pprint(response.headers)
+            pprint.pprint(response.data)
+            print("--------------------------------------------------------------------------------")
+        self.assertEqual(response.status_code, status_code)
+        if content_checks:
+            for cch in content_checks:
+                self.assertTrue(cch in response.data)
+        return response
+
+    def assertPostURL(self, url, data, status_code = 200, content_checks = None, print_response = False, follow_redirects = True):  # pylint: disable=locally-disabled,invalid-name
+        """
+        Perform POST request and check some default assertions against the response.
+        """
+        response = self.client.post(
+            url,
+            data = data,
+            follow_redirects = follow_redirects
+        )
+        if print_response:
+            print("--------------------------------------------------------------------------------")
+            print("Response for POST {}, {}: {} ({})".format(url, pprint.pformat(data), response.status_code, response.status))
+            pprint.pprint(response.headers)
+            pprint.pprint(response.data)
+            print("--------------------------------------------------------------------------------")
+        self.assertEqual(response.status_code, status_code)
+        if content_checks:
+            for cch in content_checks:
+                self.assertTrue(cch in response.data)
+        return response
+
+    def assertMailbox(self, checklist):  # pylint: disable=locally-disabled,invalid-name
+        """
+        Check internal mailbox.
+        """
+        for attr_name in ('subject', 'sender', 'recipients', 'cc', 'bcc', 'body', 'html'):
+            if attr_name in checklist:
+                self.assertEqual(
+                    list(
+                        map(
+                            lambda x: getattr(x, attr_name),
+                            self.mailbox,
+                        )
+                    ),
+                    checklist[attr_name]
+                )
+
+class ItemCreateVialTestCase(VialTestCase):
+    """
+    Class for testing :py:class:`vial.app.Vial` application item creation views.
+    """
+    maxDiff = None
+
+    def assertCreate(self, item_model, url, data, content_checks = None, print_response = False):  # pylint: disable=locally-disabled,invalid-name
+        """
+        Perform attempt to create given item.
+        """
+
+        # Verify, that the item form correctly displays.
+        response = self.assertGetURL(
+            url,
+            200,
+            [
+                '<form method="POST" action="{}" id="form-{}-create'.format(
+                    url,
+                    item_model.__name__.lower()
+                ).encode('utf8'),
+                b'<div class="btn-toolbar" role="toolbar" aria-label="Form submission buttons">'
+            ],
+            print_response = print_response
+        )
+
+        # Attempt to send empty item form. There is always at least one mandatory
+        # form field, so we should get some "This field is required." error.
+        request_data = {'submit': 'Submit'}
+        response = self.assertPostURL(
+            url,
+            request_data,
+            200,
+            [
+                b'This field is required.',
+                b'help-block form-error'
+            ],
+            print_response = print_response
+        )
+
+        # Attempt to send form with some mandatory fields missing.
+        #for idx, param in enumerate(data):
+        #    if idx == len(data) - 1:
+        #        break
+        #    response = self.client.post(
+        #        url,
+        #        follow_redirects = True,
+        #        data = {
+        #            i[0]: i[1] for i in data[0:idx+1]
+        #        }
+        #    )
+        #    self.assertEqual(response.status_code, 200)
+        #    self.assertTrue(b'This field is required.' in response.data)
+        #    self.assertTrue(b'help-block form-error' in response.data)
+
+        # Attempt to send form with valid data.
+        request_data = {
+            i[0]: i[1] for i in data
+        }
+        request_data['submit'] = 'Submit'
+        response = self.assertPostURL(
+            url,
+            request_data,
+            200,
+            [
+                b'<div class="alert alert-success alert-dismissible">'
+            ],
+            print_response = print_response
+        )
+        if content_checks:
+            for cch in content_checks:
+                self.assertTrue(cch in response.data)
+        return response
+
+
+class RegistrationVialTestCase(VialTestCase):
+    """
+    Class for testing :py:class:`vial.app.Vial` application registration views.
+    """
+    maxDiff = None
+
     user_fixture = {
         'apikey': None,
         'email': 'test.user@domain.org',
@@ -31,3 +466,136 @@ class RegistrationHawatTestCase(RegistrationVialTestCase):
         'roles': ['user'],
         'timezone': None
     }
+
+    def assertRegisterFail(self, url, data, environ_base = None):  # pylint: disable=locally-disabled,invalid-name
+        response = response = self.client.get(
+            url,
+            follow_redirects = True,
+            environ_base = environ_base,
+        )
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue(b'User account registration' in response.data)
+
+        for idx, param in enumerate(data):
+            if idx == len(data) - 1:
+                break
+            response = response = self.client.post(
+                url,
+                follow_redirects = True,
+                environ_base = environ_base,
+                data = {
+                    i[0]: i[1] for i in data[0:idx+1]
+                }
+            )
+            self.assertEqual(response.status_code, 200)
+            self.assertTrue(b'This field is required.' in response.data)
+            self.assertTrue(b'help-block form-error' in response.data)
+
+        response = response = self.client.post(
+            url,
+            follow_redirects = True,
+            environ_base = environ_base,
+            data = {
+                i[0]: i[1] for i in data
+            }
+        )
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue(
+            b'Please use different login, the &#34;user&#34; is already taken.' in response.data or \
+            b'Please use different login, the "user" is already taken.' in response.data
+        )
+
+
+    def assertRegister(self, url, data, emails, environ_base = None):  # pylint: disable=locally-disabled,invalid-name
+        uname = 'test'
+        self.mailbox_monitoring('on')
+
+        response = response = self.client.get(
+            url,
+            follow_redirects = True,
+            environ_base = environ_base
+        )
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue(b'User account registration' in response.data)
+
+        for idx, param in enumerate(data):
+            if idx == len(data) - 1:
+                break
+            response = response = self.client.post(
+                url,
+                follow_redirects = True,
+                environ_base = environ_base,
+                data = {
+                    i[0]: i[1] for i in data[0:idx+1]
+                }
+            )
+            self.assertEqual(response.status_code, 200)
+            self.assertTrue(b'This field is required.' in response.data)
+            self.assertTrue(b'help-block form-error' in response.data)
+
+        response = response = self.client.post(
+            url,
+            follow_redirects = True,
+            environ_base = environ_base,
+            data = {
+                i[0]: i[1] for i in data
+            }
+        )
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue(b'User account <strong>test (Test User)</strong> was successfully registered.' in response.data)
+        with self.app.app_context():
+            uobj = self.user_get(uname)
+            self.assertTrue(uobj)
+        self.assertMailbox(
+            {
+                'subject': [
+                    '[{}] Account registration - {}'.format(self.app.config['APPLICATION_NAME'], uname),
+                    '[{}] Account registration - {}'.format(self.app.config['APPLICATION_NAME'], uname)
+                ],
+                'sender': [
+                    'root@unittest',
+                    'root@unittest'
+                ],
+                'recipients': [
+                    ['admin@unittest'],
+                    ['test.user@domain.org']
+                ],
+                'cc': [[],[]],
+                'bcc': [[], ['admin@unittest']],
+                'body': emails['txt'],
+                'html': emails['html']
+            }
+        )
+
+        self.mailbox_monitoring('off')
+
+        with self.app.app_context():
+            user = self.user_get(uname)
+            user_dict = user.to_dict()
+            del user_dict['createtime']
+            del user_dict['password']
+            self.assertEqual(
+                user_dict,
+                self.user_fixture
+            )
+        response = self.login_dev(uname)
+        self.assertEqual(response.status_code, 403)
+        #self.assertTrue(b'is currently disabled, you are not permitted to log in.' in response.data)
+
+        with self.app.app_context():
+            user = self.user_get(uname)
+            user.set_state_enabled()
+            self.user_save(user)
+
+        with self.app.app_context():
+            user = self.user_get(uname)
+            user_dict = user.to_dict()
+            del user_dict['createtime']
+            del user_dict['password']
+        response = self.login_dev(uname)
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue(b'You have been successfully logged in as' in response.data)
+
+        response = self.logout()
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue(b'You have been successfully logged out' in response.data)
diff --git a/lib/vial/test/fixtures.py b/lib/hawat/test/fixtures.py
similarity index 100%
rename from lib/vial/test/fixtures.py
rename to lib/hawat/test/fixtures.py
diff --git a/lib/hawat/test/runner.py b/lib/hawat/test/runner.py
index 542a71581..0a027bebc 100644
--- a/lib/hawat/test/runner.py
+++ b/lib/hawat/test/runner.py
@@ -13,7 +13,7 @@ Base library for Hawat unit tests.
 """
 
 import hawat.const
-import vial.test.fixtures
+import hawat.test.fixtures
 
 from mentat.datatype.sqldb import UserModel, GroupModel, ItemChangeLogModel, SettingsReportingModel, FilterModel, NetworkModel
 from mentat.const import CKEY_CORE_DATABASE, CKEY_CORE_DATABASE_EVENTSTORAGE
@@ -66,7 +66,7 @@ class TestRunnerMixin:
         )
 
     def get_fixtures_db(self, app):
-        fixture_list = vial.test.fixtures.get_fixtures_db(app)
+        fixture_list = hawat.test.fixtures.get_fixtures_db(app)
         for fixture in fixture_list:
             if isinstance(fixture, app.get_model(hawat.const.MODEL_USER)):
                 fixture.organization = 'BOGUS DOMAIN, a.l.e.'
diff --git a/lib/vial/__init__.py b/lib/vial/__init__.py
deleted file mode 100644
index 077a2c7a6..000000000
--- a/lib/vial/__init__.py
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-*Vial* is a lightweight skeleton application with batteries included built on top
-of excelent `Flask <http://flask.pocoo.org/>`__ microframework.
-"""
-
-
-__author__  = "Honza Mach <honza.mach.ml@gmail.com>"
-__version__ = "1.0.0"
-
-
-import os
-
-
-def create_app_full(  # pylint: disable=locally-disabled,too-many-arguments
-        app_class,
-        app_name,
-        config_dict   = None,
-        config_object = 'vial.config.ProductionConfig',
-        config_file   = None,
-        config_env    = 'VIAL_CONFIG_FILE',
-        config_func   = None):
-    """
-    Factory function for building Vial application. This function takes number of
-    optional arguments, that can be used to create a very customized instance of
-    Vial application. This can be very usefull when extending applications`
-    capabilities or for purposes of testing. Each of these arguments has default
-    value for the most common application setup, so for disabling it entirely it
-    is necessary to provide ``None`` as a value.
-
-    :param class app_class: Flask application class to instantinate.
-    :param string app_name: Name of the application, identifier in lowercase.
-    :param dict config_dict: Initial default configurations.
-    :param str config_object: Name of a class or module containing configurations.
-    :param str config_file: Name of a file containing configurations.
-    :param str config_env: Name of an environment variable pointing to a file containing configurations.
-    :param callable config_func: Callable that will receive app.config as parameter.
-    :return: Vial application
-    :rtype: vial.app.VialApp
-    """
-
-    app = app_class(app_name)
-
-    if config_dict and isinstance(config_dict, dict):
-        app.config.update(config_dict)
-    if config_object:
-        app.config.from_object(config_object)
-    if config_file:
-        app.config.from_pyfile(config_file)
-    if config_env and os.getenv(config_env, None):
-        app.config.from_envvar(config_env)
-    if config_func and callable(config_func):
-        config_func(app.config)
-
-    app.setup_app()
-
-    return app
diff --git a/lib/vial/app.py b/lib/vial/app.py
deleted file mode 100644
index bd33d7e62..000000000
--- a/lib/vial/app.py
+++ /dev/null
@@ -1,1038 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-This module contains extended :py:class:`flask.Flask` and :py:class:`flask.Blueprint`
-classes that form the new base of *Vial* application.
-"""
-
-
-import sys
-import traceback
-import datetime
-import weakref
-import json
-import jinja2
-
-import werkzeug.routing
-import werkzeug.utils
-import flask
-import flask.app
-import flask.views
-import flask_babel
-import flask_migrate
-import flask_login
-import flask_principal
-
-import hawat.const
-import vial.acl
-import vial.log
-import vial.mailer
-import vial.intl
-import vial.errors
-import vial.utils
-import vial.jsglue
-import vial.view
-import vial.menu
-import vial.command
-
-
-class VialException(Exception):
-    """
-    Custom class for :py:class:`vial.app.Vial` application exceptions.
-    """
-
-
-class Vial(flask.Flask):  # pylint: disable=locally-disabled,too-many-instance-attributes
-    """
-    Custom implementation of :py:class:`flask.Flask` class. This class extends the
-    capabilities of the base class with following additional features:
-
-    Configuration based blueprint registration
-        The application configuration file contains a directive describing list
-        of requested blueprints/modules, that should be registered into the
-        application. This enables administrator to very easily fine tune the
-        application setup for each installation. See the :py:func:`vial.app.Vial.register_blueprints`
-        for more information on the topic.
-
-    Application main menu management
-        The application provides three distinct menus, that are at a disposal for
-        blueprint/module designer.
-    """
-
-    def __init__(self, import_name, **kwargs):
-        super().__init__(import_name, **kwargs)
-
-        self.csrf = None
-
-        self.mailer = None
-
-        self.menu_main = vial.menu.Menu()
-        self.menu_auth = vial.menu.Menu()
-        self.menu_anon = vial.menu.Menu()
-
-        self.sign_ins    = {}
-        self.sign_ups    = {}
-        self.resources   = {}
-        self.infomailers = {}
-
-    @property
-    def icons(self):
-        """
-        Application icon registry.
-        """
-        return self.config.get('ICONS')
-
-    @flask.app.setupmethod
-    def add_url_rule(self, rule, endpoint = None, view_func = None, provide_automatic_options = None, **options):
-        """
-        Reimplementation of :py:func:`flask.Flask.add_url_rule` method. This method
-        is capable of disabling selected application endpoints. Keep in mind, that
-        some URL rules (like application global 'static' endpoint) are created during
-        the :py:func:`flask.app.Flask.__init__` method and cannot be disabled,
-        because at that point the configuration of the application is not yet loaded.
-        """
-        if self.config.get('DISABLED_ENDPOINTS', None) and self.config['DISABLED_ENDPOINTS'] and endpoint:
-            if endpoint in self.config['DISABLED_ENDPOINTS']:
-                self.logger.warning(  # pylint: disable=locally-disabled,no-member
-                    "Application endpoint '%s' is disabled by configuration.",
-                    endpoint
-                )
-                return
-        #self.logger.debug(  # pylint: disable=locally-disabled,no-member
-        #    "Registering URL route %s:%s:%s:%s",
-        #    str(rule),
-        #    str(endpoint),
-        #    str(view_func),
-        #    str(view_func.view_class) if hasattr(view_func, 'view_class') else '---none---',
-        #)
-        super().add_url_rule(rule, endpoint, view_func, provide_automatic_options, **options)
-
-    def register_blueprint(self, blueprint, **options):
-        """
-        Reimplementation of :py:func:`flask.Flask.register_blueprint` method. This
-        method will perform standart blueprint registration and on top of that will
-        perform following additional tasks:
-
-            * Register blueprint into custom internal registry. The registry lies
-              within application`s ``config`` under key :py:const:`hawat.const.CFGKEY_ENABLED_BLUEPRINTS`.
-            * Call blueprint`s ``register_app`` method, if available, with ``self`` as only argument.
-
-        :param vial.app.VialBlueprint blueprint: Blueprint to be registered.
-        :param dict options: Additional options, will be passed down to :py:func:`flask.Flask.register_blueprint`.
-        """
-        super().register_blueprint(blueprint, **options)
-
-        if isinstance(blueprint, VialBlueprint):
-            if hasattr(blueprint, 'register_app'):
-                blueprint.register_app(self)
-
-            self.sign_ins.update(blueprint.sign_ins)
-            self.sign_ups.update(blueprint.sign_ups)
-
-    def register_blueprints(self):
-        """
-        Register all configured application blueprints. The configuration comes
-        from :py:const:`hawat.const.CFGKEY_ENABLED_BLUEPRINTS` configuration
-        subkey, which must contain list of string names of required blueprints.
-        The blueprint module must provide ``get_blueprint`` factory method, that
-        must return valid instance of :py:class:`vial.app.VialBlueprint`. This
-        method will call the :py:func:`vial.app.Vial.register_blueprint` for
-        each blueprint, that is being registered into the application.
-
-        :raises vial.app.VialException: In case the factory method ``get_blueprint`` is not provided by loaded module.
-        """
-        for name in self.config[hawat.const.CFGKEY_ENABLED_BLUEPRINTS]:
-            self.logger.debug(  # pylint: disable=locally-disabled,no-member
-                "Loading pluggable module %s",
-                name
-            )
-            mod = werkzeug.utils.import_string(name)
-            if hasattr(mod, 'get_blueprint'):
-                self.register_blueprint(mod.get_blueprint())
-            else:
-                raise VialException(
-                    "Invalid blueprint module '{}', does not provide the 'get_blueprint' factory method.".format(name)
-                )
-
-    def log_exception(self, exc_info):
-        """
-        Reimplementation of :py:func:`flask.Flask.log_exception` method.
-        """
-        self.logger.error(  # pylint: disable=locally-disabled,no-member
-            "Exception on %s [%s]" % (flask.request.full_path, flask.request.method),
-            exc_info = exc_info
-        )
-
-    def log_exception_with_label(self, tbexc, label = ''):
-        """
-        Log given exception traceback into application logger.
-        """
-        self.logger.error(  # pylint: disable=locally-disabled,no-member
-            '%s%s',
-            label,
-            ''.join(tbexc.format())
-        )
-
-    #--------------------------------------------------------------------------
-
-    def get_modules(self, filter_func = None):
-        """
-        Get all currently registered application modules.
-        """
-        if not filter_func:
-            return self.blueprints
-        return {
-            k: v for k, v in self.blueprints.items() if filter_func(k, v)
-        }
-
-    def has_endpoint(self, endpoint):
-        """
-        Check if given routing endpoint is available.
-
-        :param str endpoint: Application routing endpoint.
-        :return: ``True`` in case endpoint exists, ``False`` otherwise.
-        :rtype: bool
-        """
-        return endpoint in self.view_functions
-
-    def get_endpoints(self, filter_func = None):
-        """
-        Get all currently registered application endpoints.
-        """
-        if not filter_func:
-            return {
-                k: v.view_class for k, v in self.view_functions.items() if hasattr(v, 'view_class')
-            }
-        return {
-            k: v.view_class for k, v in self.view_functions.items() if hasattr(v, 'view_class') and filter_func(k, v.view_class)
-        }
-
-    def get_endpoint_class(self, endpoint, quiet = False):
-        """
-        Get reference to view class registered to given routing endpoint.
-
-        :param str endpoint: Application routing endpoint.
-        :param bool quiet: Suppress the exception in case given endpoint does not exist.
-        :return: Reference to view class.
-        :rtype: class
-        """
-        if not endpoint in self.view_functions:
-            if quiet:
-                return None
-            raise VialException(
-                "Unknown endpoint name '{}'.".format(endpoint)
-            )
-        try:
-            return self.view_functions[endpoint].view_class
-        except AttributeError:
-            return vial.view.DecoratedView(self.view_functions[endpoint])
-
-    def can_access_endpoint(self, endpoint, **kwargs):
-        """
-        Check, that the current user can access given endpoint/view.
-
-        :param str endpoint: Application routing endpoint.
-        :param dict kwargs: Optional endpoint parameters.
-        :return: ``True`` in case user can access the endpoint, ``False`` otherwise.
-        :rtype: bool
-        """
-        try:
-            view_class = self.get_endpoint_class(endpoint)
-
-            # Reject unauthenticated users in case view requires authentication.
-            if view_class.authentication:
-                if not flask_login.current_user.is_authenticated:
-                    return False
-
-            # Check view authorization rules.
-            if view_class.authorization:
-                for auth_rule in view_class.authorization:
-                    if not auth_rule.can():
-                        return False
-
-            # Check item action authorization callback, if exists.
-            if hasattr(view_class, 'authorize_item_action'):
-                if not view_class.authorize_item_action(**kwargs):
-                    return False
-
-            return True
-
-        except VialException:
-            return False
-
-    def get_model(self, name):
-        """
-        Return reference to class of given model.
-
-        :param str name: Name of the model.
-        """
-        return self.config[hawat.const.CFGKEY_MODELS][name]
-
-    def get_resource(self, name):
-        """
-        Return reference to given registered resource.
-
-        :param str name: Name of the resource.
-        """
-        return self.resources[name]()
-
-    def set_resource(self, name, resource):
-        """
-        Store reference to given resource.
-
-        :param str name: Name of the resource.
-        :param resource: Resource to be registered.
-        """
-        self.resources[name] = weakref.ref(resource)
-
-    def set_infomailer(self, name, mailer):
-        """
-        Register mailer handle to be usable by different web interface components.
-
-        :param str name: Name of the informailer.
-        :param callable mailer: Mailer handle.
-        """
-        self.infomailers.setdefault(name, []).append(mailer)
-
-    def send_infomail(self, name, **kwargs):
-        """
-        Send emails through all registered infomailer handles.
-
-        :param str name: Name of the informailer.
-        :param **kwargs: Additional mailer arguments.
-        """
-        for mailer in self.infomailers[name]:
-            mailer(**kwargs)
-
-    #--------------------------------------------------------------------------
-
-    def setup_app(self):
-        """
-        Perform setup of the whole application.
-        """
-        self._setup_app_logging()
-        self._setup_app_mailer()
-        self._setup_app_core()
-        self._setup_app_db()
-        self._setup_app_auth()
-        self._setup_app_acl()
-        self._setup_app_intl()
-        self._setup_app_menu()
-        self._setup_app_blueprints()
-        self._setup_app_cli()
-
-    def _setup_app_logging(self):
-        """
-        Setup logging to file and via email for given Vial application. Logging
-        capabilities are adjustable by application configuration.
-
-        :param vial.app.VialApp app: Vial application to be modified.
-        :return: Modified Vial application
-        :rtype: vial.app.VialApp
-        """
-        vial.log.setup_logging_default(self)
-        vial.log.setup_logging_file(self)
-        if not self.debug:
-            vial.log.setup_logging_email(self)
-
-        return self
-
-    def _setup_app_mailer(self):
-        """
-        Setup mailer service for Vial application.
-
-        :param vial.app.VialApp app: Vial application to be modified.
-        :return: Modified Vial application
-        :rtype: vial.app.VialApp
-        """
-        vial.mailer.MAILER.init_app(self)
-        self.mailer = vial.mailer.MAILER
-
-        return self
-
-    def _setup_app_core(self):
-        """
-        Setup application core for given Vial application. The application core
-        contains following features:
-
-            * Error handlers
-            * Default routes
-            * Additional custom Jinja template variables
-            * Additional custom Jinja template macros
-
-        :param vial.app.VialApp app: Vial application to be modified.
-        :return: Modified Vial application
-        :rtype: vial.app.VialApp
-        """
-        @self.errorhandler(400)
-        def eh_badrequest(err):  # pylint: disable=locally-disabled,unused-variable
-            """Flask error handler to be called to service HTTP 400 error."""
-            flask.current_app.logger.critical(
-                "BAD REQUEST\n\nRequest: %s\nTraceback:\n%s",
-                flask.request.full_path,
-                ''.join(
-                    traceback.TracebackException(
-                        *sys.exc_info()
-                    ).format()
-                )
-            )
-            return vial.errors.error_handler_switch(400, err)
-
-        @self.errorhandler(403)
-        def eh_forbidden(err):  # pylint: disable=locally-disabled,unused-variable
-            """Flask error handler to be called to service HTTP 403 error."""
-            return vial.errors.error_handler_switch(403, err)
-
-        @self.errorhandler(404)
-        def eh_page_not_found(err):  # pylint: disable=locally-disabled,unused-variable
-            """Flask error handler to be called to service HTTP 404 error."""
-            return vial.errors.error_handler_switch(404, err)
-
-        @self.errorhandler(405)
-        def eh_method_not_allowed(err):  # pylint: disable=locally-disabled,unused-variable
-            """Flask error handler to be called to service HTTP 405 error."""
-            return vial.errors.error_handler_switch(405, err)
-
-        @self.errorhandler(410)
-        def eh_gone(err):  # pylint: disable=locally-disabled,unused-variable
-            """Flask error handler to be called to service HTTP 410 error."""
-            return vial.errors.error_handler_switch(410, err)
-
-        @self.errorhandler(500)
-        def eh_internal_server_error(err):  # pylint: disable=locally-disabled,unused-variable
-            """Flask error handler to be called to service HTTP 500 error."""
-            flask.current_app.logger.critical(
-                "INTERNAL SERVER ERROR\n\nRequest: %s\nTraceback:\n%s",
-                flask.request.full_path,
-                ''.join(
-                    traceback.TracebackException(
-                        *sys.exc_info()
-                    ).format()
-                ),
-            )
-            return vial.errors.error_handler_switch(500, err)
-
-        @self.before_request
-        def before_request():  # pylint: disable=locally-disabled,unused-variable
-            """
-            Use Flask`s :py:func:`flask.Flask.before_request` hook for performing
-            various usefull tasks before each request.
-            """
-            flask.g.requeststart = datetime.datetime.utcnow()
-
-        @self.context_processor
-        def jinja_inject_variables():  # pylint: disable=locally-disabled,unused-variable
-            """
-            Inject additional variables into Jinja2 global template namespace.
-            """
-            return dict(
-                vial_appname           = flask.current_app.config['APPLICATION_NAME'],
-                vial_appid             = flask.current_app.config['APPLICATION_ID'],
-                vial_current_app       = flask.current_app,
-                vial_current_menu_main = flask.current_app.menu_main,
-                vial_current_menu_auth = flask.current_app.menu_auth,
-                vial_current_menu_anon = flask.current_app.menu_anon,
-                vial_current_view      = self.get_endpoint_class(flask.request.endpoint, True),
-                vial_logger            = flask.current_app.logger,
-                vial_cdt_utc           = datetime.datetime.utcnow(),
-                vial_cdt_local         = datetime.datetime.now(),
-            )
-
-        @self.context_processor
-        def jinja2_inject_functions():  # pylint: disable=locally-disabled,unused-variable,too-many-locals
-            """
-            Register additional helpers into Jinja2 global template namespace.
-            """
-            def get_modules_dict():
-                """
-                Return dictionary of all registered application pluggable modules.
-                """
-                return flask.current_app.blueprints
-
-            def get_endpoints_dict():
-                """
-                Return dictionary of all registered application view endpoints.
-                """
-                return { k: v.view_class for k, v in flask.current_app.view_functions.items() if hasattr(v, 'view_class') }
-
-            def get_endpoint_class(endpoint, quiet = False):
-                """
-                Return class reference to given view endpoint.
-
-                :param str endpoint: Name of the view endpoint.
-                :param bool quiet: Suppress the exception in case given endpoint does not exist.
-                """
-                return self.get_endpoint_class(endpoint, quiet)
-
-            def check_endpoint_exists(endpoint):
-                """
-                Check, that given application view endpoint exists and is registered within
-                the application.
-
-                :param str endpoint: Name of the view endpoint.
-                :return: ``True`` in case endpoint exists, ``False`` otherwise.
-                :rtype: bool
-                """
-                return endpoint in self.view_functions
-
-            def get_icon(icon_name, default_icon = 'missing-icon'):
-                """
-                Get HTML icon markup for given icon.
-
-                :param str icon_name: Name of the icon.
-                :param str default_icon: Name of the default icon.
-                :return: Icon including HTML markup.
-                :rtype: flask.Markup
-                """
-                return flask.Markup(
-                    self.config.get('ICONS').get(
-                        icon_name,
-                        self.config.get('ICONS').get(default_icon)
-                    )
-                )
-
-            def get_module_icon(endpoint, default_icon = 'missing-icon'):
-                """
-                Get HTML icon markup for parent module of given view endpoint.
-
-                :param str endpoint: Name of the view endpoint.
-                :param str default_icon: Name of the default icon.
-                :return: Icon including HTML markup.
-                :rtype: flask.Markup
-                """
-                return flask.Markup(
-                    self.config.get('ICONS').get(
-                        self.get_endpoint_class(endpoint).module_ref().get_module_icon(),
-                        self.config.get('ICONS').get(default_icon)
-                    )
-                )
-
-            def get_endpoint_icon(endpoint, default_icon = 'missing-icon'):
-                """
-                Get HTML icon markup for given view endpoint.
-
-                :param str endpoint: Name of the view endpoint.
-                :return: Icon including HTML markup.
-                :rtype: flask.Markup
-                """
-                return flask.Markup(
-                    self.config.get('ICONS').get(
-                        self.get_endpoint_class(endpoint).get_view_icon(),
-                        self.config.get('ICONS').get(default_icon)
-                    )
-                )
-
-            def get_country_flag(country):
-                """
-                Get URL to static country flag file.
-
-                :param str country: Name of the icon.
-                :return: Country including HTML markup.
-                :rtype: flask.Markup
-                """
-                if not hawat.const.CRE_COUNTRY_CODE.match(country):
-                    return get_icon('flag')
-
-                return flask.Markup(
-                    '<img src="{}">'.format(
-                        flask.url_for(
-                            'static',
-                            filename = 'images/country-flags/flags-iso/shiny/16/{}.png'.format(
-                                country.upper()
-                            )
-                        )
-                    )
-                )
-
-            def include_raw(filename):
-                """
-                Include given file in raw form directly into the generated content.
-                This may be usefull for example for including JavaScript files
-                directly into the HTML page.
-                """
-                return jinja2.Markup(
-                    self.jinja_loader.get_source(self.jinja_env, filename)[0]
-                )
-
-            return dict(
-                get_modules_dict      = get_modules_dict,
-                get_endpoints_dict    = get_endpoints_dict,
-                get_endpoint_class    = get_endpoint_class,
-                check_endpoint_exists = check_endpoint_exists,
-
-                get_icon          = get_icon,
-                get_module_icon   = get_module_icon,
-                get_endpoint_icon = get_endpoint_icon,
-                get_country_flag  = get_country_flag,
-
-                get_timedelta       = vial.utils.get_timedelta,
-                get_datetime_utc    = vial.utils.get_datetime_utc,
-                get_datetime_local  = vial.utils.get_datetime_local,
-                parse_datetime      = vial.utils.parse_datetime,
-
-                get_datetime_window = vial.view.mixin.VialUtils.get_datetime_window,
-
-                check_file_exists = vial.utils.check_file_exists,
-
-                in_query_params       = vial.utils.in_query_params,
-                generate_query_params = vial.utils.generate_query_params,
-
-                current_datetime_utc = datetime.datetime.utcnow(),
-
-                include_raw         = include_raw,
-                json_to_yaml        = vial.utils.json_to_yaml,
-                get_uuid4           = vial.utils.get_uuid4,
-                load_json_from_file = vial.utils.load_json_from_file,
-                make_copy_deep      = vial.utils.make_copy_deep
-            )
-
-        @self.template_filter('tojson_pretty')
-        def to_pretty_json(value):
-            return json.dumps(
-                value,
-                sort_keys = True,
-                indent = 4
-            )
-
-        class VialJSONEncoder(flask.json.JSONEncoder):
-            """
-            Custom JSON encoder for converting anything into JSON strings.
-            """
-            def default(self, obj):  # pylint: disable=locally-disabled,method-hidden,arguments-differ
-                try:
-                    if isinstance(obj, datetime.datetime):
-                        return obj.isoformat() + 'Z'
-                except:  # pylint: disable=locally-disabled,bare-except
-                    pass
-                try:
-                    return obj.to_dict()
-                except:  # pylint: disable=locally-disabled,bare-except
-                    pass
-                try:
-                    return str(obj)
-                except:  # pylint: disable=locally-disabled,bare-except
-                    pass
-                return flask.json.JSONEncoder.default(self, obj)
-
-        self.json_encoder = VialJSONEncoder
-
-        @self.route('/app-main.js')
-        def mainjs():  # pylint: disable=locally-disabled,unused-variable
-            """
-            Default route for main application JavaScript file.
-            """
-            return flask.make_response(
-                flask.render_template('app-main.js'),
-                200,
-                {'Content-Type': 'text/javascript'}
-            )
-
-        # Initialize JSGlue plugin for using `flask.url_for()` method in JavaScript.
-        #jsglue = flask_jsglue.JSGlue()
-        jsglue = vial.jsglue.JSGlue()
-        jsglue.init_app(self)
-
-        @self.template_filter()
-        def pprint_item(item):  # pylint: disable=locally-disabled,unused-variable
-            """
-            Custom Jinja2 filter for full object attribute dump/pretty-print.
-            """
-            res = []
-            for key in dir(item):
-                res.append('%r: %r' % (key, getattr(item, key)))
-            return '\n'.join(res)
-
-        return self
-
-    def _setup_app_db(self):
-        """
-        Setup application database service for given Vial application.
-
-        :param vial.app.VialApp app: Vial application to be modified.
-        :return: Modified Vial application
-        :rtype: vial.app.VialApp
-        """
-        dbh = vial.db.db_setup(**self.config['SQLALCHEMY_SETUP_ARGS'])
-        dbh.init_app(self)
-
-        # Initialize database migration service and register it among the application
-        # resources for possible future use.
-        migrate = flask_migrate.Migrate(
-            app       = self,
-            db        = dbh,
-            directory = self.config['MIGRATE_DIRECTORY']
-        )
-        self.set_resource(hawat.const.RESOURCE_MIGRATE, migrate)
-
-        self.logger.debug(
-            "Connected to database via SQLAlchemy ({})".format(
-                self.config['SQLALCHEMY_DATABASE_URI']
-            )
-        )
-
-        return self
-
-
-    def _setup_app_auth(self):
-        """
-        Setup application authentication features.
-
-        :param vial.app.VialApp app: Vial application to be modified.
-        :return: Modified Vial application
-        :rtype: vial.app.VialApp
-        """
-
-        lim = flask_login.LoginManager()
-        lim.init_app(self)
-        lim.login_view = self.config['ENDPOINT_LOGIN']
-        lim.login_message = flask_babel.gettext("Please log in to access this page.")
-        lim.login_message_category = self.config['LOGIN_MSGCAT']
-
-        self.set_resource(hawat.const.RESOURCE_LOGIN_MANAGER, lim)
-
-        @lim.user_loader
-        def load_user(user_id):  # pylint: disable=locally-disabled,unused-variable
-            """
-            Flask-Login callback for loading current user`s data.
-            """
-            user_model = self.get_model(hawat.const.MODEL_USER)
-            return vial.db.db_get().session.query(user_model).filter(user_model.id == user_id).one_or_none()
-
-        @self.route('/logout')
-        @flask_login.login_required
-        def logout():  # pylint: disable=locally-disabled,unused-variable
-            """
-            Flask-Login callback for logging out current user.
-            """
-            flask.current_app.logger.info(
-                "User '{}' just logged out.".format(
-                    str(flask_login.current_user)
-                )
-            )
-            flask_login.logout_user()
-            flask.flash(
-                flask_babel.gettext('You have been successfully logged out.'),
-                hawat.const.FLASH_SUCCESS
-            )
-
-            # Remove session keys set by Flask-Principal.
-            for key in ('identity.name', 'identity.auth_type'):
-                flask.session.pop(key, None)
-
-            # Tell Flask-Principal the identity changed.
-            flask_principal.identity_changed.send(
-                flask.current_app._get_current_object(),  # pylint: disable=locally-disabled,protected-access
-                identity = flask_principal.AnonymousIdentity()
-            )
-
-            # Force user to index page.
-            return flask.redirect(
-                flask.url_for(
-                    flask.current_app.config['ENDPOINT_LOGOUT_REDIRECT']
-                )
-            )
-
-        return self
-
-    def _setup_app_acl(self):
-        """
-        Setup application ACL features.
-
-        :param vial.app.VialApp app: Vial application to be modified.
-        :return: Modified Vial application
-        :rtype: vial.app.VialApp
-        """
-        fpp = flask_principal.Principal(self, skip_static = True)
-        self.set_resource(hawat.const.RESOURCE_PRINCIPAL, fpp)
-
-        @flask_principal.identity_loaded.connect_via(self)
-        def on_identity_loaded(sender, identity):  # pylint: disable=locally-disabled,unused-variable,unused-argument
-            """
-            Flask-Principal callback for populating user identity object after login.
-            """
-            # Set the identity user object.
-            identity.user = flask_login.current_user
-
-            if not flask_login.current_user.is_authenticated:
-                flask.current_app.logger.debug(
-                    "Loaded ACL identity for anonymous user '{}'.".format(
-                        str(flask_login.current_user)
-                    )
-                )
-                return
-            flask.current_app.logger.debug(
-                "Loading ACL identity for user '{}'.".format(
-                    str(flask_login.current_user)
-                )
-            )
-
-            # Add the UserNeed to the identity.
-            if hasattr(flask_login.current_user, 'get_id'):
-                identity.provides.add(
-                    flask_principal.UserNeed(flask_login.current_user.id)
-                )
-
-            # Assuming the User model has a list of roles, update the
-            # identity with the roles that the user provides.
-            if hasattr(flask_login.current_user, 'roles'):
-                for role in flask_login.current_user.roles:
-                    identity.provides.add(
-                        flask_principal.RoleNeed(role)
-                    )
-
-            # Assuming the User model has a list of group memberships, update the
-            # identity with the groups that the user is member of.
-            if hasattr(flask_login.current_user, 'memberships'):
-                for group in flask_login.current_user.memberships:
-                    identity.provides.add(
-                        vial.acl.MembershipNeed(group.id)
-                    )
-
-            # Assuming the User model has a list of group managements, update the
-            # identity with the groups that the user is manager of.
-            if hasattr(flask_login.current_user, 'managements'):
-                for group in flask_login.current_user.managements:
-                    identity.provides.add(
-                        vial.acl.ManagementNeed(group.id)
-                    )
-
-        @self.context_processor
-        def utility_acl_processor():  # pylint: disable=locally-disabled,unused-variable
-            """
-            Register additional helpers related to authorization into Jinja global
-            namespace to enable them within the templates.
-            """
-            def can_access_endpoint(endpoint, item = None):
-                """
-                Check if currently logged-in user can access given endpoint/view.
-
-                :param str endpoint: Name of the application endpoint.
-                :param item: Optional item for additional validations.
-                :return: ``True`` in case user can access the endpoint, ``False`` otherwise.
-                :rtype: bool
-                """
-                return flask.current_app.can_access_endpoint(endpoint, item = item)
-
-            def permission_can(permission_name):
-                """
-                Manually check currently logged-in user for given permission.
-
-                :param str permission_name: Name of the permission.
-                :return: Check result.
-                :rtype: bool
-                """
-                return vial.acl.PERMISSIONS[permission_name].can()
-
-            def is_it_me(item):
-                """
-                Check if given user account is mine.
-                """
-                return item.id == flask_login.current_user.id
-
-            return dict(
-                can_access_endpoint = can_access_endpoint,
-                permission_can      = permission_can,
-                is_it_me            = is_it_me
-            )
-
-        return self
-
-    def _setup_app_intl(self):
-        """
-        Setup application`s internationalization sybsystem.
-
-        :param vial.app.VialApp app: Vial application to be modified.
-        :return: Modified Vial application
-        :rtype: vial.app.VialApp
-        """
-        vial.intl.BABEL.init_app(self)
-        self.set_resource(hawat.const.RESOURCE_BABEL, vial.intl.BABEL)
-
-        @self.route('/locale/<code>')
-        def locale(code):  # pylint: disable=locally-disabled,unused-variable
-            """
-            Application route providing users with the option of changing locale.
-            """
-            if code not in flask.current_app.config['SUPPORTED_LOCALES']:
-                return flask.abort(404)
-
-            if flask_login.current_user.is_authenticated:
-                flask_login.current_user.locale = code
-                # Make sure current user is in SQLAlchemy session. Turns out, this
-                # step is not necessary and current user is already in session,
-                # because it was fetched from database few moments ago.
-                #vial.db.db_session().add(flask_login.current_user)
-                vial.db.db_session().commit()
-
-            flask.session['locale'] = code
-            flask_babel.refresh()
-
-            flask.flash(
-                flask.Markup(flask_babel.gettext(
-                    'Locale was succesfully changed to <strong>%(lcln)s (%(lclc)s)</strong>.',
-                    lclc = code,
-                    lcln = flask.current_app.config['SUPPORTED_LOCALES'][code]
-                )),
-                hawat.const.FLASH_SUCCESS
-            )
-
-            # Redirect user back to original page.
-            return flask.redirect(
-                vial.forms.get_redirect_target(
-                    default_url = flask.url_for(
-                        flask.current_app.config['ENDPOINT_HOME']
-                    )
-                )
-            )
-
-        @self.before_request
-        def before_request():  # pylint: disable=locally-disabled,unused-variable
-            """
-            Use Flask`s :py:func:`flask.Flask.before_request` hook for storing
-            currently selected locale and timezone to request`s session storage.
-            """
-            if 'locale' not in flask.session:
-                flask.session['locale'] = vial.intl.get_locale()
-            if 'timezone' not in flask.session:
-                flask.session['timezone'] = vial.intl.get_timezone()
-
-        @self.context_processor
-        def utility_processor():  # pylint: disable=locally-disabled,unused-variable
-            """
-            Register additional internationalization helpers into Jinja global namespace.
-            """
-
-            return dict(
-                babel_get_locale         = vial.intl.get_locale,
-                babel_get_timezone       = vial.intl.get_timezone,
-                babel_format_datetime    = flask_babel.format_datetime,
-                babel_format_date        = flask_babel.format_date,
-                babel_format_time        = flask_babel.format_time,
-                babel_format_timedelta   = flask_babel.format_timedelta,
-                babel_format_decimal     = flask_babel.format_decimal,
-                babel_format_percent     = flask_babel.format_percent,
-                babel_format_bytes       = vial.intl.babel_format_bytes,
-                babel_translate_locale   = vial.intl.babel_translate_locale,
-                babel_language_in_locale = vial.intl.babel_language_in_locale
-            )
-
-        return self
-
-    def _setup_app_menu(self):
-        """
-        Setup default application menu skeleton.
-
-        :param vial.app.VialApp app: Vial application to be modified.
-        :return: Modified Vial application
-        :rtype: vial.app.VialApp
-        """
-        for entry in self.config[hawat.const.CFGKEY_MENU_MAIN_SKELETON]:
-            self.menu_main.add_entry(**entry)
-
-        return self
-
-    def _setup_app_blueprints(self):
-        """
-        Setup application blueprints.
-
-        :param vial.app.VialApp app: Vial application to be modified.
-        :return: Modified Vial application
-        :rtype: vial.app.VialApp
-        """
-        self.register_blueprints()
-
-        return self
-
-    def _setup_app_cli(app):
-        """
-        Setup application command line interface.
-
-        :param vial.app.VialApp app: Vial application to be modified.
-        :return: Modified Vial application
-        :rtype: vial.app.VialApp
-        """
-        vial.command.setup_cli(app)
-
-        return app
-
-
-class VialBlueprint(flask.Blueprint):
-    """
-    Custom implementation of :py:class:`flask.Blueprint` class. This class extends
-    the capabilities of the base class with additional features:
-
-        * Support for better integration into application and registration of view classes.
-        * Support for custom tweaking of application object.
-        * Support for custom style of authentication and authorization decorators
-    """
-    def __init__(self, name, import_name, **kwargs):
-        super().__init__(name, import_name, **kwargs)
-
-        self.sign_ins     = {}
-        self.sign_ups     = {}
-
-    @classmethod
-    def get_module_title(cls):
-        """
-        Get human readable name for this blueprint/module.
-
-        :return: Name (short summary) of the blueprint/module.
-        :rtype: str
-        """
-        raise NotImplementedError()
-
-    def get_module_icon(self):
-        """
-        Return icon name for the module. Given name will be used as index to
-        built-in icon registry.
-
-        :return: Icon for the module.
-        :rtype: str
-        """
-        return 'module-{}'.format(self.name).replace('_', '-')
-
-    def register_app(self, app):  # pylint: disable=locally-disabled,no-self-use,unused-argument
-        """
-        *Hook method:* Custom callback, which will be called from
-        :py:func:`vial.app.Vial.register_blueprint` method and which can
-        perform additional tweaking of Vial application object.
-
-        :param vial.app.Vial app: Application object.
-        """
-        return
-
-    def register_view_class(self, view_class, route_spec):
-        """
-        Register given view class into the internal blueprint registry.
-
-        :param vial.view.BaseView view_class: View class (not instance!)
-        :param str route_spec: Routing information for the view.
-        """
-        view_class.module_ref  = weakref.ref(self)
-        view_class.module_name = self.name
-
-        # Obtain view function.
-        view_func = view_class.as_view(view_class.get_view_name())
-
-        # Apply authorization decorators (if requested).
-        if view_class.authorization:
-            for auth in view_class.authorization:
-                view_func = auth.require(403)(view_func)
-
-        # Apply authentication decorators (if requested).
-        if view_class.authentication:
-            view_func = flask_login.login_required(view_func)
-
-        # Register endpoint to the application.
-        self.add_url_rule(route_spec, view_func = view_func)
-
-        # Register SIGN IN and SIGN UP views to enable further special handling.
-        if hasattr(view_class, 'is_sign_in') and view_class.is_sign_in:
-            self.sign_ins[view_class.get_view_endpoint()] = view_class
-        if hasattr(view_class, 'is_sign_up') and view_class.is_sign_up:
-            self.sign_ups[view_class.get_view_endpoint()] = view_class
diff --git a/lib/vial/blueprints/__init__.py b/lib/vial/blueprints/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/lib/vial/blueprints/auth_env/__init__.py b/lib/vial/blueprints/auth_env/__init__.py
deleted file mode 100644
index 4160e87f1..000000000
--- a/lib/vial/blueprints/auth_env/__init__.py
+++ /dev/null
@@ -1,290 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-This pluggable module provides default authentication service based on server
-environment. In this case the burden of performing actual authentication is
-on the web server used for serving the web interface. The authentication module
-then simply uses selected environment variables set up by the server after
-successfull authentication.
-
-This module also provides interface for automated user account registration. The
-registration form is pre-filled with data gathered again from server environment.
-The login may not be changed and the value fetched from environment is always used.
-Other account attributes like name or email address may be tweaked by user before
-submitting the registration form. Administrator and user are both notified via
-email about the fact new account was just created.
-
-
-Environment variables
---------------------------------------------------------------------------------
-
-Currently following environment variables set up by the HTTP server are supported:
-
-``eppn``,``REMOTE_USER`` (*MANDATORY*)
-    The ``eppn`` server variable is set up by the _shibd_ daemon implementing the
-    Shibboleth SSO service. The ``REMOTE_USER`` variable is set up by many
-    authentication providers. This environment variable is of course mandatory
-    and it is used as an account username (login).
-
-``cn``,``givenName``,``sn`` (*OPTIONAL*)
-    The ``cn`` server variable is used to fill in user`s name, when available.
-    When not available, user`s name is constructed as contatenation of ``givenName``
-    and ``sn`` server variables. When none of the above is available, user has to
-    input his/her name manually during registration process.
-
-``perunPreferredMail``,``mail`` (*OPTIONAL*)
-    The ``perunPreferredMail`` server variable is used to fill in user`s email
-    address, when available. When not available, the first email address from
-    ``email`` server variable is used. When none of the above is available, user
-    has to input his/her email manually during registration process.
-
-``perunOrganizationName``,``o`` (*OPTIONAL*)
-    The ``perunOrganizationName`` server variable is used to fill in user`s home
-    organization name, when available. When not available, the value of ``o``
-    server variable is used. When none of the above is available, user
-    has to input his/her home organization name manually during registration process.
-
-
-Provided endpoints
---------------------------------------------------------------------------------
-
-``/auth_env/login``
-    Page providing login functionality via server set environment variables.
-
-    * *Authentication:* no authentication
-    * *Methods:* ``GET``
-
-``/auth_env/register``
-    User account registration using server set environment variables.
-
-    * *Authentication:* no authentication
-    * *Methods:* ``GET``, ``POST``
-"""
-
-
-import flask
-from flask_babel import gettext, lazy_gettext
-
-import hawat.const
-import vial.forms
-import vial.db
-from vial.app import VialBlueprint
-from vial.view import BaseLoginView, BaseRegisterView
-from vial.view.mixin import HTMLMixin, SQLAlchemyMixin
-
-from vial.blueprints.auth_env.forms import RegisterUserAccountForm
-
-
-BLUEPRINT_NAME = 'auth_env'
-"""Name of the blueprint as module global constant."""
-
-
-class RegistrationException(Exception):
-    """
-    Exception describing problems with new user account registration.
-    """
-    def __init__(self, description):
-        super().__init__()
-        self.description = description
-
-    def __str__(self):
-        return str(self.description)
-
-
-def get_login_from_environment():
-    """
-    Get user account login from appropriate environment variable(s).
-    """
-    return flask.request.environ.get(
-        'eppn',
-        flask.request.environ.get('REMOTE_USER', None)
-    )
-
-class LoginView(HTMLMixin, SQLAlchemyMixin, BaseLoginView):
-    """
-    View responsible for user login via application environment.
-    """
-    methods = ['GET']
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('Environment login')
-
-    @classmethod
-    def get_menu_title(cls, **kwargs):
-        return lazy_gettext('Login (env)')
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def search_by(self):
-        return self.dbmodel.login
-
-    def get_user_login(self):
-        user_login = get_login_from_environment()
-        if not user_login:
-            self.flash(
-                gettext('User login was not received, unable to perform login process.'),
-                hawat.const.FLASH_FAILURE
-            )
-            self.abort(403)
-        return user_login
-
-
-class RegisterView(HTMLMixin, SQLAlchemyMixin, BaseRegisterView):
-    """
-    View responsible for registering new user account into application.
-    """
-    methods = ['GET', 'POST']
-
-    @classmethod
-    def get_menu_title(cls, **kwargs):
-        return lazy_gettext('Register (env)')
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('User account registration (env)')
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    def get_user_from_env(self):
-        """
-        Get user object populated with information gathered from server environment
-        variables.
-        """
-        item = self.dbmodel()
-
-        # Fetch login from server authentication headers (mandatory).
-        item.login = get_login_from_environment()
-        if not item.login:
-            raise RegistrationException(
-                gettext("Unable to retrieve account login from your authentication provider.")
-            )
-
-        # Try to fetch name from server authentication headers (optional).
-        while True:
-            try:
-                item.fullname = flask.request.environ['cn']
-                break
-            except (KeyError, AttributeError):
-                pass
-            try:
-                item.fullname = '{} {}'.format(
-                    flask.request.environ['givenName'],
-                    flask.request.environ['sn']
-                )
-                break
-            except (KeyError, AttributeError):
-                pass
-            break
-
-        # Try to fetch email from server authentication headers (optional).
-        while True:
-            try:
-                item.email = flask.request.environ['perunPreferredMail']
-                break
-            except (KeyError, AttributeError):
-                pass
-            try:
-                item.email = flask.request.environ['mail'].split(';')[0]
-                break
-            except (KeyError, AttributeError):
-                pass
-            break
-
-        # Try to fetch organization from server authentication headers (optional).
-        while True:
-            try:
-                item.organization = flask.request.environ['perunOrganizationName']
-                break
-            except (KeyError, AttributeError):
-                pass
-            try:
-                item.organization = flask.request.environ['o']
-                break
-            except (KeyError, AttributeError):
-                pass
-            break
-
-        return item
-
-    def get_item(self):
-        # Attempt to create user object from server environment variables.
-        try:
-            return self.get_user_from_env()
-        except RegistrationException as exc:
-            self.abort(500, exc)
-
-    @staticmethod
-    def get_item_form(item):
-        locales = list(
-            flask.current_app.config['SUPPORTED_LOCALES'].items()
-        )
-
-        return RegisterUserAccountForm(
-            obj = item,
-            choices_locales = locales
-        )
-
-
-    def dispatch_request(self):
-        """
-        Mandatory interface required by the :py:func:`flask.views.View.dispatch_request`.
-        Will be called by the *Flask* framework to service the request.
-        """
-        self.response_context.update(
-            apacheenv = flask.request.environ
-        )
-        return super().dispatch_request()
-
-
-#-------------------------------------------------------------------------------
-
-
-class EnvAuthBlueprint(VialBlueprint):
-    """Pluggable module - environment authentication service (*auth_env*)."""
-
-    @classmethod
-    def get_module_title(cls):
-        return lazy_gettext('Environment authentication service')
-
-    def register_app(self, app):
-        app.set_infomailer('auth_env.register', RegisterView.inform_admins)
-        app.set_infomailer('auth_env.register', RegisterView.inform_managers)
-        app.set_infomailer('auth_env.register', RegisterView.inform_user)
-
-
-#-------------------------------------------------------------------------------
-
-
-def get_blueprint():
-    """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
-    :py:class:`flask.Blueprint`.
-    """
-
-    hbp = EnvAuthBlueprint(
-        BLUEPRINT_NAME,
-        __name__,
-        template_folder = 'templates',
-        url_prefix = '/{}'.format(BLUEPRINT_NAME)
-    )
-
-    hbp.register_view_class(LoginView,    '/login')
-    hbp.register_view_class(RegisterView, '/register')
-
-    return hbp
diff --git a/lib/vial/blueprints/auth_env/forms.py b/lib/vial/blueprints/auth_env/forms.py
deleted file mode 100644
index 8b3610ec8..000000000
--- a/lib/vial/blueprints/auth_env/forms.py
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-This module contains custom user account registration form for Hawat.
-"""
-
-
-import wtforms
-from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField
-
-from flask_babel import lazy_gettext
-
-from vial.forms import get_available_groups
-
-from vial.blueprints.users.forms import BaseUserAccountForm
-
-
-class RegisterUserAccountForm(BaseUserAccountForm):
-    """
-    Class representing user account registration form.
-    """
-    memberships_wanted = QuerySelectMultipleField(
-        lazy_gettext('Requested group memberships:'),
-        query_factory = get_available_groups
-    )
-    justification = wtforms.TextAreaField(
-        lazy_gettext('Justification:'),
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.Length(min = 10, max = 500)
-        ]
-    )
diff --git a/lib/vial/blueprints/auth_env/templates/auth_env/registration.html b/lib/vial/blueprints/auth_env/templates/auth_env/registration.html
deleted file mode 100644
index e6f9ac35b..000000000
--- a/lib/vial/blueprints/auth_env/templates/auth_env/registration.html
+++ /dev/null
@@ -1,26 +0,0 @@
-{%- extends "_layout_registration.html" %}
-
-{%- block registrationformfields %}
-                                {{ macros_form.render_form_item_static(_('Login:'), item.login) }}
-                                {{ macros_form.render_form_item_default(form.fullname) }}
-                                {{ macros_form.render_form_item_default(form.email) }}
-                                {{ macros_form.render_form_item_default(form.justification) }}
-
-                                <hr>
-
-                                {{ macros_form.render_form_item_select(form.memberships_wanted) }}
-
-                                <hr>
-
-                                {{ macros_form.render_form_item_select(form.locale) }}
-                                {{ macros_form.render_form_item_select(form.timezone) }}
-
-                                <hr>
-
-{%- if permission_can('developer') %}
-                                <pre class="pre-scrollable">
-{{ apacheenv | pprint }}
-                                </pre>
-                                <hr>
-    {%- endif -%}
-{%- endblock registrationformfields %}
diff --git a/lib/vial/blueprints/auth_env/templates/auth_env/registration/email_admins.txt b/lib/vial/blueprints/auth_env/templates/auth_env/registration/email_admins.txt
deleted file mode 100644
index 5a58a2339..000000000
--- a/lib/vial/blueprints/auth_env/templates/auth_env/registration/email_admins.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-{{ _('Dear administrator,') | wordwrap }}
-
-{{ _('a new account "%(item_id)s" was just registered in %(app_name)s. Please review the following request and activate or delete the account:', item_id = account.login, app_name = vial_appname) | wordwrap }}
-
-    {{ '{:16s}'.format(_('Login:')) }} {{ account.login }}
-    {{ '{:16s}'.format(_('Full name:')) }} {{ account.fullname }}
-    {{ '{:16s}'.format(_('Email:')) }} {{ account.email }}
-{%- if account.memberships_wanted %}
-
-{{ _('User has requested membership in following groups:') | wordwrap }}
-{%- for group in account.memberships_wanted %}
- * {{ group.name }}
-{%- endfor %}
-{%- endif %}
-
-{{ _('User has provided following justification to be given access to the system:') | wordwrap }}
-
-{{ justification | wordwrap(width=75, break_long_words=False) | indent(width=4, first=True) }}
-
-{{ _('Account details can be found here:') | wordwrap }}
-
-  {{ url_for('users.show', _external = True, item_id = account.id ) }}
-
-{{ _('Have a nice day') | wordwrap }}
-
--- {{ vial_appname }}
diff --git a/lib/vial/blueprints/auth_env/templates/auth_env/registration/email_managers.txt b/lib/vial/blueprints/auth_env/templates/auth_env/registration/email_managers.txt
deleted file mode 100644
index 6cc8bf534..000000000
--- a/lib/vial/blueprints/auth_env/templates/auth_env/registration/email_managers.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-{{ _('Dear group manager,') | wordwrap }}
-
-{{ _('a new account "%(item_id)s" was just registered in %(app_name)s and user requested membership in your group "%(group_id)s". Please review the following information and approve or reject the request:', item_id = account.login, app_name = vial_appname, group_id = group.name) | wordwrap }}
-
-    {{ '{:16s}'.format(_('Login:')) }} {{ account.login }}
-    {{ '{:16s}'.format(_('Full name:')) }} {{ account.fullname }}
-    {{ '{:16s}'.format(_('Email:')) }} {{ account.email }}
-
-{{ _('User has provided following justification to be given access to the system:') | wordwrap }}
-
-{{ justification | wordwrap(width=75, break_long_words=False) | indent(width=4, first=True) }}
-
-{{ _('Management page for your group can be found here:') | wordwrap }}
-
-  {{ url_for('groups.show', _external = True, item_id = group.id ) }}
-
-{{ _('Have a nice day') | wordwrap }}
-
--- {{ vial_appname }}
diff --git a/lib/vial/blueprints/auth_env/templates/auth_env/registration/email_user.txt b/lib/vial/blueprints/auth_env/templates/auth_env/registration/email_user.txt
deleted file mode 100644
index a3f2d82c9..000000000
--- a/lib/vial/blueprints/auth_env/templates/auth_env/registration/email_user.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-{{ _('Dear user,') | wordwrap }}
-
-{{ _('this email is a confirmation, that you have successfully registered your new user account "%(item_id)s" in %(app_name)s.', item_id = account.login, app_name = vial_appname) | wordwrap }}
-
-{{ _('During the registration process you have provided following information:') | wordwrap }}
-
-    {{ '{:16s}'.format(_('Login:')) }} {{ account.login }}
-    {{ '{:16s}'.format(_('Full name:')) }} {{ account.fullname }}
-    {{ '{:16s}'.format(_('Email:')) }} {{ account.email }}
-{%- if account.memberships_wanted %}
-
-{{ _('You have requested membership in following groups:') | wordwrap }}
-{%- for group in account.memberships_wanted %}
- * {{ group.name }}
-{%- endfor %}
-{%- endif %}
-
-{{ _('You have provided following justification to be given access to the system:') | wordwrap }}
-
-{{ justification | wordwrap(width=75, break_long_words=False) | indent(width=4, first=True) }}
-
-{{ _('Administrator was informed about registration of a new account. You will receive email confirmation when your account will be activated.') | wordwrap }}
-
-{{ _('After successfull activation you will be able to login and start using the system:') | wordwrap }}
-
-	{{ url_for('auth.login', _external = True ) }}
-
-{{ _('Have a nice day') | wordwrap }}
-
--- {{ vial_appname }}
diff --git a/lib/vial/blueprints/auth_env/test/__init__.py b/lib/vial/blueprints/auth_env/test/__init__.py
deleted file mode 100644
index 1f1e9261c..000000000
--- a/lib/vial/blueprints/auth_env/test/__init__.py
+++ /dev/null
@@ -1,188 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-Unit tests for :py:mod:`vial.blueprints.auth_env`.
-"""
-
-import sys
-import unittest
-
-import hawat.const
-import vial.test
-import vial.db
-from vial.test import RegistrationVialTestCase
-from vial.test.runner import TestRunnerMixin
-
-
-_IS_NOSE = sys.argv[0].endswith('nosetests')
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class AuthEnvTestCase(TestRunnerMixin, RegistrationVialTestCase):
-    """
-    Class for testing :py:mod:`vial.blueprints.auth_env` blueprint.
-    """
-
-    def test_01_login_user(self):
-        """
-        Test login/logout with *auth_env* module - user 'user'.
-        """
-        response = self.login_env(hawat.const.ROLE_USER)
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged in as' in response.data)
-
-        response = self.logout()
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged out' in response.data)
-
-        response = self.login_env(hawat.const.ROLE_USER, 'REMOTE_USER')
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged in as' in response.data)
-
-        response = self.logout()
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged out' in response.data)
-
-    def test_02_login_developer(self):
-        """
-        Test login/logout with *auth_env* module - user 'developer'.
-        """
-        response = self.login_env(hawat.const.ROLE_DEVELOPER)
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged in as' in response.data)
-
-        response = self.logout()
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged out' in response.data)
-
-        response = self.login_env(hawat.const.ROLE_DEVELOPER, 'REMOTE_USER')
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged in as' in response.data)
-
-        response = self.logout()
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged out' in response.data)
-
-    def test_03_login_admin(self):
-        """
-        Test login/logout with *auth_env* module - user 'admin'.
-        """
-        response = self.login_env(hawat.const.ROLE_ADMIN)
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged in as' in response.data)
-
-        response = self.logout()
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged out' in response.data)
-
-        response = self.login_env(hawat.const.ROLE_ADMIN, 'REMOTE_USER')
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged in as' in response.data)
-
-        response = self.logout()
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged out' in response.data)
-
-    def test_04_register(self):
-        """
-        Test registration with *auth_env* module - new user 'test'.
-        """
-        self.assertRegister(
-            '/auth_env/register',
-            [
-                ('submit', 'Register'),
-                ('justification', 'I really want in.')
-            ],
-            {
-                'txt': [
-                    'Dear administrator,\n'
-                    '\n'
-                    'a new account "test" was just registered in Vial. Please review the '
-                    'following\n'
-                    'request and activate or delete the account:\n'
-                    '\n'
-                    '    Login:           test\n'
-                    '    Full name:       Test User\n'
-                    '    Email:           test.user@domain.org\n'
-                    '\n'
-                    'User has provided following justification to be given access to the system:\n'
-                    '\n'
-                    '    I really want in.\n'
-                    '\n'
-                    'Account details can be found here:\n'
-                    '\n'
-                    '  http://localhost/users/5/show\n'
-                    '\n'
-                    'Have a nice day\n'
-                    '\n'
-                    '-- Vial',
-                    'Dear user,\n'
-                    '\n'
-                    'this email is a confirmation, that you have successfully registered your '
-                    'new\n'
-                    'user account "test" in Vial.\n'
-                    '\n'
-                    'During the registration process you have provided following information:\n'
-                    '\n'
-                    '    Login:           test\n'
-                    '    Full name:       Test User\n'
-                    '    Email:           test.user@domain.org\n'
-                    '\n'
-                    'You have provided following justification to be given access to the system:\n'
-                    '\n'
-                    '    I really want in.\n'
-                    '\n'
-                    'Administrator was informed about registration of a new account. You will\n'
-                    'receive email confirmation when your account will be activated.\n'
-                    '\n'
-                    'After successfull activation you will be able to login and start using the\n'
-                    'system:\n'
-                    '\n'
-                    '\thttp://localhost/auth/login\n'
-                    '\n'
-                    'Have a nice day\n'
-                    '\n'
-                    '-- Vial'
-                ],
-                'html': [
-                    None,
-                    None
-                ]
-            },
-            {
-                'eppn': 'test',
-                'cn': 'Test User',
-                'givenName': 'Test',
-                'sn': 'User',
-                'perunPreferredMail': 'test.user@domain.org',
-            }
-        )
-
-    def test_05_register_fail(self):
-        """
-        Test registration with *auth_env* module - existing user 'user'.
-        """
-        self.assertRegisterFail(
-            '/auth_env/register',
-            [
-                ('submit', 'Register'),
-                ('organization', 'TEST, org.'),
-                ('justification', 'I really want in.')
-            ],
-            {
-                'eppn': 'user',
-                'cn': 'Test User',
-                'perunPreferredMail': 'test.user@domain.org',
-            }
-        )
-
-
-#-------------------------------------------------------------------------------
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/lib/vial/blueprints/auth_pwd/__init__.py b/lib/vial/blueprints/auth_pwd/__init__.py
deleted file mode 100644
index d37cae49d..000000000
--- a/lib/vial/blueprints/auth_pwd/__init__.py
+++ /dev/null
@@ -1,148 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-This pluggable module provides classical web login form with password authentication
-method.
-
-
-Provided endpoints
---------------------------------------------------------------------------------
-
-``/auth_pwd/login``
-    Page providing classical web login form.
-
-    * *Authentication:* no authentication
-    * *Methods:* ``GET``, ``POST``
-"""
-
-
-import flask
-from flask_babel import lazy_gettext
-
-import hawat.const
-import vial.forms
-from vial.app import VialBlueprint
-from vial.view import BaseLoginView, BaseRegisterView
-from vial.view.mixin import HTMLMixin, SQLAlchemyMixin
-
-from vial.blueprints.auth_pwd.forms import LoginForm, RegisterUserAccountForm
-
-
-BLUEPRINT_NAME = 'auth_pwd'
-"""Name of the blueprint as module global constant."""
-
-
-class LoginView(HTMLMixin, SQLAlchemyMixin, BaseLoginView):
-    """
-    View enabling classical password login.
-    """
-    methods = ['GET', 'POST']
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('Password login')
-
-    @classmethod
-    def get_menu_title(cls, **kwargs):
-        return lazy_gettext('Login (pwd)')
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def search_by(self):
-        return self.dbmodel.login
-
-    def get_user_login(self):
-        form = LoginForm()
-        self.response_context.update(
-            form = form
-        )
-        if form.validate_on_submit():
-            return form.login.data
-        return None
-
-    def authenticate_user(self, user):
-        return user.check_password(
-            self.response_context['form'].password.data
-        )
-
-
-class RegisterView(HTMLMixin, SQLAlchemyMixin, BaseRegisterView):
-    """
-    View enabling classical password login.
-    """
-    methods = ['GET', 'POST']
-
-    @classmethod
-    def get_menu_title(cls, **kwargs):
-        return lazy_gettext('Register (pwd)')
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('User account registration (pwd)')
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @property
-    def search_by(self):
-        return self.dbmodel.login
-
-    @staticmethod
-    def get_item_form(item):
-        locales = list(
-            flask.current_app.config['SUPPORTED_LOCALES'].items()
-        )
-        return RegisterUserAccountForm(
-            choices_locales = locales
-        )
-
-    def do_before_action(self, item):  # pylint: disable=locally-disabled,no-self-use,unused-argument
-        super().do_before_action(item)
-        item.set_password(item.password)
-
-
-#-------------------------------------------------------------------------------
-
-
-class PwdAuthBlueprint(VialBlueprint):
-    """Pluggable module - classical authentication service (*auth_pwd*)."""
-
-    @classmethod
-    def get_module_title(cls):
-        return lazy_gettext('Password authentication service')
-
-
-#-------------------------------------------------------------------------------
-
-
-def get_blueprint():
-    """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
-    :py:class:`flask.Blueprint`.
-    """
-
-    hbp = PwdAuthBlueprint(
-        BLUEPRINT_NAME,
-        __name__,
-        template_folder = 'templates',
-        url_prefix = '/{}'.format(BLUEPRINT_NAME)
-    )
-
-    hbp.register_view_class(LoginView,    '/login')
-    hbp.register_view_class(RegisterView, '/register')
-
-    return hbp
diff --git a/lib/vial/blueprints/auth_pwd/forms.py b/lib/vial/blueprints/auth_pwd/forms.py
deleted file mode 100644
index c588b75ba..000000000
--- a/lib/vial/blueprints/auth_pwd/forms.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of MyDojo package (https://github.com/honzamach/mydojo).
-#
-# Copyright (C) since 2018 Honza Mach <honza.mach.ml@gmail.com>
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-This module contains custom developer login form for Hawat.
-"""
-
-
-__author__ = "Honza Mach <honza.mach.ml@gmail.com>"
-
-
-import wtforms
-import flask_wtf
-from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField
-from flask_babel import lazy_gettext
-
-from vial.forms import check_login, check_unique_login, get_available_groups
-from vial.blueprints.users.forms import BaseUserAccountForm
-
-
-class LoginForm(flask_wtf.FlaskForm):
-    """
-    Class representing classical password authentication login form.
-    """
-    login = wtforms.StringField(
-        lazy_gettext('Login:'),
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.Length(min = 3, max = 50),
-            check_login
-        ]
-    )
-    password = wtforms.PasswordField(
-        lazy_gettext('Password:'),
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.Length(min = 8),
-        ]
-    )
-    submit = wtforms.SubmitField(
-        lazy_gettext('Login')
-    )
-
-
-class RegisterUserAccountForm(BaseUserAccountForm):
-    """
-    Class representing classical account registration form.
-    """
-    login = wtforms.StringField(
-        lazy_gettext('Login:'),
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.Length(min = 3, max = 50),
-            check_login,
-            check_unique_login
-        ]
-    )
-    memberships_wanted = QuerySelectMultipleField(
-        lazy_gettext('Requested group memberships:'),
-        query_factory = get_available_groups
-    )
-    justification = wtforms.TextAreaField(
-        lazy_gettext('Justification:'),
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.Length(min = 10, max = 500)
-        ]
-    )
-    password = wtforms.PasswordField(
-        lazy_gettext('Password:'),
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.Length(min = 8),
-        ]
-    )
-    password2 = wtforms.PasswordField(
-        lazy_gettext('Repeat Password:'),
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.EqualTo('password'),
-        ]
-    )
diff --git a/lib/vial/blueprints/auth_pwd/templates/auth_pwd/login.html b/lib/vial/blueprints/auth_pwd/templates/auth_pwd/login.html
deleted file mode 100644
index ba68267a6..000000000
--- a/lib/vial/blueprints/auth_pwd/templates/auth_pwd/login.html
+++ /dev/null
@@ -1,6 +0,0 @@
-{%- extends "_layout_login.html" %}
-
-{%- block loginformfields %}
-                                {{ macros_form.render_form_item_default(form.login) }}
-                                {{ macros_form.render_form_item_default(form.password) }}
-{%- endblock loginformfields %}
diff --git a/lib/vial/blueprints/auth_pwd/templates/auth_pwd/registration.html b/lib/vial/blueprints/auth_pwd/templates/auth_pwd/registration.html
deleted file mode 100644
index 18bad8fe9..000000000
--- a/lib/vial/blueprints/auth_pwd/templates/auth_pwd/registration.html
+++ /dev/null
@@ -1,24 +0,0 @@
-{%- extends "_layout_registration.html" %}
-
-{%- block registrationformfields %}
-                                {{ macros_form.render_form_item_default(form.login) }}
-                                {{ macros_form.render_form_item_default(form.fullname) }}
-                                {{ macros_form.render_form_item_default(form.email) }}
-                                {{ macros_form.render_form_item_default(form.justification) }}
-
-                                <hr>
-
-                                {{ macros_form.render_form_item_select(form.memberships_wanted) }}
-
-                                <hr>
-
-                                {{ macros_form.render_form_item_default(form.password) }}
-                                {{ macros_form.render_form_item_default(form.password2) }}
-
-                                <hr>
-
-                                {{ macros_form.render_form_item_default(form.locale) }}
-                                {{ macros_form.render_form_item_default(form.timezone) }}
-
-                                <hr>
-{%- endblock registrationformfields %}
diff --git a/lib/vial/blueprints/auth_pwd/templates/registration/email_admins.txt b/lib/vial/blueprints/auth_pwd/templates/registration/email_admins.txt
deleted file mode 100644
index 5a58a2339..000000000
--- a/lib/vial/blueprints/auth_pwd/templates/registration/email_admins.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-{{ _('Dear administrator,') | wordwrap }}
-
-{{ _('a new account "%(item_id)s" was just registered in %(app_name)s. Please review the following request and activate or delete the account:', item_id = account.login, app_name = vial_appname) | wordwrap }}
-
-    {{ '{:16s}'.format(_('Login:')) }} {{ account.login }}
-    {{ '{:16s}'.format(_('Full name:')) }} {{ account.fullname }}
-    {{ '{:16s}'.format(_('Email:')) }} {{ account.email }}
-{%- if account.memberships_wanted %}
-
-{{ _('User has requested membership in following groups:') | wordwrap }}
-{%- for group in account.memberships_wanted %}
- * {{ group.name }}
-{%- endfor %}
-{%- endif %}
-
-{{ _('User has provided following justification to be given access to the system:') | wordwrap }}
-
-{{ justification | wordwrap(width=75, break_long_words=False) | indent(width=4, first=True) }}
-
-{{ _('Account details can be found here:') | wordwrap }}
-
-  {{ url_for('users.show', _external = True, item_id = account.id ) }}
-
-{{ _('Have a nice day') | wordwrap }}
-
--- {{ vial_appname }}
diff --git a/lib/vial/blueprints/auth_pwd/templates/registration/email_managers.txt b/lib/vial/blueprints/auth_pwd/templates/registration/email_managers.txt
deleted file mode 100644
index 6cc8bf534..000000000
--- a/lib/vial/blueprints/auth_pwd/templates/registration/email_managers.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-{{ _('Dear group manager,') | wordwrap }}
-
-{{ _('a new account "%(item_id)s" was just registered in %(app_name)s and user requested membership in your group "%(group_id)s". Please review the following information and approve or reject the request:', item_id = account.login, app_name = vial_appname, group_id = group.name) | wordwrap }}
-
-    {{ '{:16s}'.format(_('Login:')) }} {{ account.login }}
-    {{ '{:16s}'.format(_('Full name:')) }} {{ account.fullname }}
-    {{ '{:16s}'.format(_('Email:')) }} {{ account.email }}
-
-{{ _('User has provided following justification to be given access to the system:') | wordwrap }}
-
-{{ justification | wordwrap(width=75, break_long_words=False) | indent(width=4, first=True) }}
-
-{{ _('Management page for your group can be found here:') | wordwrap }}
-
-  {{ url_for('groups.show', _external = True, item_id = group.id ) }}
-
-{{ _('Have a nice day') | wordwrap }}
-
--- {{ vial_appname }}
diff --git a/lib/vial/blueprints/auth_pwd/templates/registration/email_user.txt b/lib/vial/blueprints/auth_pwd/templates/registration/email_user.txt
deleted file mode 100644
index a3f2d82c9..000000000
--- a/lib/vial/blueprints/auth_pwd/templates/registration/email_user.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-{{ _('Dear user,') | wordwrap }}
-
-{{ _('this email is a confirmation, that you have successfully registered your new user account "%(item_id)s" in %(app_name)s.', item_id = account.login, app_name = vial_appname) | wordwrap }}
-
-{{ _('During the registration process you have provided following information:') | wordwrap }}
-
-    {{ '{:16s}'.format(_('Login:')) }} {{ account.login }}
-    {{ '{:16s}'.format(_('Full name:')) }} {{ account.fullname }}
-    {{ '{:16s}'.format(_('Email:')) }} {{ account.email }}
-{%- if account.memberships_wanted %}
-
-{{ _('You have requested membership in following groups:') | wordwrap }}
-{%- for group in account.memberships_wanted %}
- * {{ group.name }}
-{%- endfor %}
-{%- endif %}
-
-{{ _('You have provided following justification to be given access to the system:') | wordwrap }}
-
-{{ justification | wordwrap(width=75, break_long_words=False) | indent(width=4, first=True) }}
-
-{{ _('Administrator was informed about registration of a new account. You will receive email confirmation when your account will be activated.') | wordwrap }}
-
-{{ _('After successfull activation you will be able to login and start using the system:') | wordwrap }}
-
-	{{ url_for('auth.login', _external = True ) }}
-
-{{ _('Have a nice day') | wordwrap }}
-
--- {{ vial_appname }}
diff --git a/lib/vial/blueprints/auth_pwd/test/__init__.py b/lib/vial/blueprints/auth_pwd/test/__init__.py
deleted file mode 100644
index 8e673ffee..000000000
--- a/lib/vial/blueprints/auth_pwd/test/__init__.py
+++ /dev/null
@@ -1,176 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-Unit tests for :py:mod:`vial.blueprints.auth_pwd`.
-"""
-
-import sys
-import unittest
-
-import hawat.const
-import vial.test
-import vial.db
-from vial.test import RegistrationVialTestCase
-from vial.test.runner import TestRunnerMixin
-
-
-_IS_NOSE = sys.argv[0].endswith('nosetests')
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class AuthPwdTestCase(TestRunnerMixin, RegistrationVialTestCase):
-    """
-    Class for testing :py:mod:`vial.blueprints.auth_pwd` blueprint.
-    """
-
-    def test_01_login_user(self):
-        """
-        Test login/logout with *auth_pwd* module - user 'user'.
-        """
-        with self.app.app_context():
-            user = self.user_get(hawat.const.ROLE_USER)
-            user.set_password('password')
-            self.user_save(user)
-
-        response = self.login_pwd(hawat.const.ROLE_USER, 'password')
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged in as' in response.data)
-
-        response = self.logout()
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged out' in response.data)
-
-    def test_02_login_developer(self):
-        """
-        Test login/logout with *auth_pwd* module - user 'developer'.
-        """
-        with self.app.app_context():
-            user = self.user_get(hawat.const.ROLE_DEVELOPER)
-            user.set_password('password')
-            self.user_save(user)
-
-        response = self.login_pwd(hawat.const.ROLE_DEVELOPER, 'password')
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged in as' in response.data)
-
-        response = self.logout()
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged out' in response.data)
-
-    def test_03_login_admin(self):
-        """
-        Test login/logout with *auth_pwd* module - user 'admin'.
-        """
-        with self.app.app_context():
-            user = self.user_get(hawat.const.ROLE_ADMIN)
-            user.set_password('password')
-            self.user_save(user)
-
-        response = self.login_pwd(hawat.const.ROLE_ADMIN, 'password')
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged in as' in response.data)
-
-        response = self.logout()
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged out' in response.data)
-
-    def test_04_register(self):
-        """
-        Test registration with *auth_pwd* module - new user 'test'.
-        """
-        self.assertRegister(
-            '/auth_pwd/register',
-            [
-                ('submit', 'Register'),
-                ('login', 'test'),
-                ('fullname', 'Test User'),
-                ('email', 'test.user@domain.org'),
-                ('justification', 'I really want in.'),
-                ('password', 'password'),
-                ('password2', 'password'),
-            ],
-            {
-                'txt': [
-                    'Dear administrator,\n'
-                    '\n'
-                    'a new account "test" was just registered in Vial. Please review the '
-                    'following\n'
-                    'request and activate or delete the account:\n'
-                    '\n'
-                    '    Login:           test\n'
-                    '    Full name:       Test User\n'
-                    '    Email:           test.user@domain.org\n'
-                    '\n'
-                    'User has provided following justification to be given access to the system:\n'
-                    '\n'
-                    '    I really want in.\n'
-                    '\n'
-                    'Account details can be found here:\n'
-                    '\n'
-                    '  http://localhost/users/5/show\n'
-                    '\n'
-                    'Have a nice day\n'
-                    '\n'
-                    '-- Vial',
-                    'Dear user,\n'
-                    '\n'
-                    'this email is a confirmation, that you have successfully registered your '
-                    'new\n'
-                    'user account "test" in Vial.\n'
-                    '\n'
-                    'During the registration process you have provided following information:\n'
-                    '\n'
-                    '    Login:           test\n'
-                    '    Full name:       Test User\n'
-                    '    Email:           test.user@domain.org\n'
-                    '\n'
-                    'You have provided following justification to be given access to the system:\n'
-                    '\n'
-                    '    I really want in.\n'
-                    '\n'
-                    'Administrator was informed about registration of a new account. You will\n'
-                    'receive email confirmation when your account will be activated.\n'
-                    '\n'
-                    'After successfull activation you will be able to login and start using the\n'
-                    'system:\n'
-                    '\n'
-                    '\thttp://localhost/auth/login\n'
-                    '\n'
-                    'Have a nice day\n'
-                    '\n'
-                    '-- Vial'
-                ],
-                'html': [
-                    None,
-                    None
-                ]
-            }
-        )
-
-    def test_05_register_fail(self):
-        """
-        Test registration with *auth_pwd* module - existing user 'user'.
-        """
-        self.assertRegisterFail(
-            '/auth_pwd/register',
-            [
-                ('submit', 'Register'),
-                ('login', 'user'),
-                ('fullname', 'Demo User'),
-                ('email', 'demo.user@domain.org'),
-                ('justification', 'I really want in.'),
-                ('password', 'password'),
-                ('password2', 'password'),
-            ]
-        )
-
-
-#-------------------------------------------------------------------------------
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/lib/vial/blueprints/groups/__init__.py b/lib/vial/blueprints/groups/__init__.py
deleted file mode 100644
index 2556a71f4..000000000
--- a/lib/vial/blueprints/groups/__init__.py
+++ /dev/null
@@ -1,1108 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-This file contains pluggable module for Vial application containing features
-related to user group management. These features include:
-
-* general group listing
-* detailed group view
-* creating new groups
-* updating existing groups
-* deleting existing groups
-* enabling existing groups
-* disabling existing groups
-* adding group members
-* removing group members
-* rejecting group membership requests
-"""
-
-
-import flask
-import flask_login
-import flask_principal
-from flask_babel import gettext, lazy_gettext
-
-from sqlalchemy import and_, or_
-
-import vial.acl
-import vial.menu
-from vial.app import VialBlueprint
-from vial.view import ItemListView, ItemShowView, ItemCreateView, ItemUpdateView, ItemDeleteView, ItemEnableView, ItemDisableView, ItemObjectRelationView
-from vial.view.mixin import HTMLMixin, SQLAlchemyMixin
-from vial.blueprints.groups.forms import AdminCreateGroupForm, AdminUpdateGroupForm, UpdateGroupForm, GroupSearchForm
-
-
-BLUEPRINT_NAME = 'groups'
-"""Name of the blueprint as module global constant."""
-
-
-class ListView(HTMLMixin, SQLAlchemyMixin, ItemListView):
-    """
-    General group listing.
-    """
-
-    methods = ['GET']
-
-    authentication = True
-
-    authorization = [vial.acl.PERMISSION_POWER]
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('Group management')
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @classmethod
-    def get_action_menu(cls):
-        action_menu = vial.menu.Menu()
-        action_menu.add_entry(
-            'endpoint',
-            'create',
-            endpoint = 'groups.create',
-            resptitle = True
-        )
-        return action_menu
-
-    @classmethod
-    def get_context_action_menu(cls):
-        action_menu = vial.menu.Menu()
-        action_menu.add_entry(
-            'endpoint',
-            'show',
-            endpoint = 'groups.show',
-            hidetitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'update',
-            endpoint = 'groups.update',
-            hidetitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'disable',
-            endpoint = 'groups.disable',
-            hidetitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'enable',
-            endpoint = 'groups.enable',
-            hidetitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'delete',
-            endpoint = 'groups.delete',
-            hidetitle = True
-        )
-        return action_menu
-
-    @staticmethod
-    def get_search_form(request_args):
-        """
-        Must return instance of :py:mod:`flask_wtf.FlaskForm` appropriate for
-        searching given type of items.
-        """
-        return GroupSearchForm(
-            request_args,
-            meta = {'csrf': False}
-        )
-
-    @staticmethod
-    def build_query(query, model, form_args):
-        # Adjust query based on text search string.
-        if 'search' in form_args and form_args['search']:
-            query = query\
-                .filter(
-                    or_(
-                        model.name.like('%{}%'.format(form_args['search'])),
-                        model.description.like('%{}%'.format(form_args['search'])),
-                    )
-                )
-        # Adjust query based on lower time boudary selection.
-        if 'dt_from' in form_args and form_args['dt_from']:
-            query = query.filter(model.createtime >= form_args['dt_from'])
-        # Adjust query based on upper time boudary selection.
-        if 'dt_to' in form_args and form_args['dt_to']:
-            query = query.filter(model.createtime <= form_args['dt_to'])
-        # Adjust query based on user state selection.
-        if 'state' in form_args and form_args['state']:
-            if form_args['state'] == 'enabled':
-                query = query.filter(model.enabled == True)
-            elif form_args['state'] == 'disabled':
-                query = query.filter(model.enabled == False)
-        # Adjust query based on record source selection.
-        if 'source' in form_args and form_args['source']:
-            query = query\
-                .filter(model.source == form_args['source'])
-        # Adjust query based on user membership selection.
-        if 'member' in form_args and form_args['member']:
-            query = query\
-                .join(model.members)\
-                .filter(model.members.any(id = form_args['member'].id))
-        # Adjust query based on user membership selection.
-        if 'manager' in form_args and form_args['manager']:
-            query = query\
-                .join(model.managers)\
-                .filter(model.managers.any(id = form_args['managers'].id))
-        if 'sortby' in form_args and form_args['sortby']:
-            sortmap = {
-                'createtime.desc': lambda x, y: x.order_by(y.createtime.desc()),
-                'createtime.asc': lambda x, y: x.order_by(y.createtime.asc()),
-                'name.desc': lambda x, y: x.order_by(y.name.desc()),
-                'name.asc': lambda x, y: x.order_by(y.name.asc())
-            }
-            query = sortmap[form_args['sortby']](query, model)
-        return query
-
-class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView):
-    """
-    Detailed group view.
-    """
-
-    methods = ['GET']
-
-    authentication = True
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        if isinstance(kwargs['item'], cls.get_model(hawat.const.MODEL_GROUP)):
-            return lazy_gettext(
-                'View details of group &quot;%(item)s&quot;',
-                item = flask.escape(str(kwargs['item']))
-            )
-        return lazy_gettext(
-            'View details of group &quot;%(item)s&quot;',
-            item = flask.escape(str(kwargs['item'].group))
-        )
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('Show group details')
-
-    @classmethod
-    def get_view_url(cls, **kwargs):
-        if isinstance(kwargs['item'], cls.get_model(hawat.const.MODEL_GROUP)):
-            return flask.url_for(
-                cls.get_view_endpoint(),
-                item_id = kwargs['item'].get_id()
-            )
-        return flask.url_for(
-            cls.get_view_endpoint(),
-            item_id = kwargs['item'].group.get_id()
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        permission_mm = flask_principal.Permission(
-            vial.acl.MembershipNeed(kwargs['item'].id),
-            vial.acl.ManagementNeed(kwargs['item'].id)
-        )
-        return vial.acl.PERMISSION_POWER.can() or permission_mm.can()
-
-    @classmethod
-    def get_action_menu(cls):
-        action_menu = vial.menu.Menu()
-        action_menu.add_entry(
-            'endpoint',
-            'update',
-            endpoint = 'groups.update',
-            resptitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'disable',
-            endpoint = 'groups.disable',
-            resptitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'enable',
-            endpoint = 'groups.enable',
-            resptitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'delete',
-            endpoint = 'groups.delete',
-            resptitle = True
-        )
-        return action_menu
-
-    def do_before_response(self, **kwargs):  # pylint: disable=locally-disabled,no-self-use,unused-argument
-        action_menu = vial.menu.Menu()
-        action_menu.add_entry(
-            'endpoint',
-            'show',
-            endpoint = 'users.show',
-            hidetitle = True,
-        )
-        action_menu.add_entry(
-            'submenu',
-            'more',
-            align_right = True,
-            legend = gettext('More actions')
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.add_membership',
-            endpoint = 'users.addmembership'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.reject_membership',
-            endpoint = 'users.rejectmembership'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.remove_membership',
-            endpoint = 'users.removemembership'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.add_management',
-            endpoint = 'users.addmanagement'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.remove_management',
-            endpoint = 'users.removemanagement'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.enable',
-            endpoint = 'users.enable'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.disable',
-            endpoint = 'users.disable'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.update',
-            endpoint = 'users.update'
-        )
-        self.response_context.update(
-            context_action_menu_users = action_menu
-        )
-
-        item = self.response_context['item']
-        if self.can_access_endpoint('groups.update', item = item) and self.has_endpoint('changelogs.search'):
-            self.response_context.update(
-                context_action_menu_changelogs = self.get_endpoint_class(
-                    'changelogs.search'
-                ).get_context_action_menu()
-            )
-            item_changelog_model = self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-            item_changelog = self.dbsession.query(item_changelog_model).\
-                filter(
-                    or_(
-                        # Changelogs related directly to group item.
-                        and_(
-                            item_changelog_model.model == item.__class__.__name__,
-                            item_changelog_model.model_id == item.id
-                        )
-                    )
-                ).\
-                order_by(item_changelog_model.createtime.desc()).\
-                limit(100).\
-                all()
-            self.response_context.update(item_changelog = item_changelog)
-
-
-class ShowByNameView(ShowView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    Detailed group view by group name.
-    """
-
-    @classmethod
-    def get_view_name(cls):
-        return 'show_by_name'
-
-    @classmethod
-    def get_view_template(cls):
-        return '{}/show.html'.format(cls.module_name)
-
-    @property
-    def search_by(self):
-        return self.dbmodel.name
-
-
-class CreateView(HTMLMixin, SQLAlchemyMixin, ItemCreateView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for creating new groups.
-    """
-
-    methods = ['GET','POST']
-
-    authentication = True
-
-    authorization = [vial.acl.PERMISSION_POWER]
-
-    @classmethod
-    def get_menu_title(cls, **kwargs):
-        return lazy_gettext('Create group')
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('Create new group')
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'Group <strong>%(item_id)s</strong> was successfully created.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext('Unable to create new group.')
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext('Canceled creating new group.')
-
-    @staticmethod
-    def get_item_form(item):
-        return AdminCreateGroupForm()
-
-
-class UpdateView(HTMLMixin, SQLAlchemyMixin, ItemUpdateView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for updating existing groups.
-    """
-
-    methods = ['GET','POST']
-
-    authentication = True
-
-    @classmethod
-    def get_menu_title(cls, **kwargs):
-        return lazy_gettext('Update')
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Update details of group &quot;%(item)s&quot;',
-            item = flask.escape(str(kwargs['item']))
-        )
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('Update group details')
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].id)
-        )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'Group <strong>%(item_id)s</strong> was successfully updated.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to update group <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled updating group <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_item_form(item):
-        admin = flask_login.current_user.has_role('admin')
-        if not admin:
-            form = UpdateGroupForm(obj = item)
-        else:
-            form = AdminUpdateGroupForm(db_item_id = item.id, obj = item)
-        return form
-
-
-class AddMemberView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for adding group members.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    @classmethod
-    def get_view_name(cls):
-        return 'addmember'
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return gettext('Add group member')
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-add-member'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Add user &quot;%(user_id)s&quot; to group &quot;%(group_id)s&quot;',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @property
-    def dbmodel_other(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].id)
-        )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
-
-    @classmethod
-    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        # Reject item change in case given item is already enabled.
-        if kwargs['other'] in kwargs['item'].members:
-            return False
-        return True
-
-    @classmethod
-    def change_item(cls, **kwargs):
-        kwargs['item'].members.append(kwargs['other'])
-        try:
-            kwargs['item'].members_wanted.remove(kwargs['other'])
-        except ValueError:
-            pass
-        if kwargs['other'].is_state_disabled():
-            kwargs['other'].set_state_enabled()
-            flask.current_app.send_infomail(
-                'users.enable',
-                account = kwargs['other']
-            )
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User <strong>%(user_id)s</strong> was successfully added as a member to group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to add user <strong>%(user_id)s</strong> as a member to group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled adding user <strong>%(user_id)s</strong> as a member to group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-
-class RejectMemberView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for rejecting group membership reuests.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    @classmethod
-    def get_view_name(cls):
-        return 'rejectmember'
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return gettext('Reject group member')
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-rej-member'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Reject user`s &quot;%(user_id)s&quot; membership request for group &quot;%(group_id)s&quot;',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @property
-    def dbmodel_other(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].id)
-        )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
-
-    @classmethod
-    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        # Reject item change in case given item is already enabled.
-        if kwargs['other'] not in kwargs['item'].members_wanted:
-            return False
-        return True
-
-    @classmethod
-    def change_item(cls, **kwargs):
-        kwargs['item'].members_wanted.remove(kwargs['other'])
-
-    #---------------------------------------------------------------------------
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User`s <strong>%(user_id)s</strong> membership request for group <strong>%(group_id)s</strong> was successfully rejected.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to reject user`s <strong>%(user_id)s</strong> membership request for group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled rejecting user`s <strong>%(user_id)s</strong> membership request for group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-
-class RemoveMemberView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for removing group members.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    @classmethod
-    def get_view_name(cls):
-        return 'removemember'
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return gettext('Remove group member')
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-rem-member'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Remove user &quot;%(user_id)s&quot; from group &quot;%(group_id)s&quot;',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @property
-    def dbmodel_other(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].id)
-        )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
-
-    @classmethod
-    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        # Reject item change in case given item is already enabled.
-        if kwargs['other'] not in kwargs['item'].members:
-            return False
-        return True
-
-    @classmethod
-    def change_item(cls, **kwargs):
-        kwargs['item'].members.remove(kwargs['other'])
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User <strong>%(user_id)s</strong> was successfully removed as a member from group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to remove user <strong>%(user_id)s</strong> as a member from group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled removing user <strong>%(user_id)s</strong> as a member from group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-
-class AddManagerView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for adding group managers.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    @classmethod
-    def get_view_name(cls):
-        return 'addmanager'
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return gettext('Add group manager')
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-add-manager'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Add user &quot;%(user_id)s&quot; to group &quot;%(group_id)s&quot; as manager',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @property
-    def dbmodel_other(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].id)
-        )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
-
-    @classmethod
-    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        # Reject item change in case given item is already manager.
-        if kwargs['other'] in kwargs['item'].managers:
-            return False
-        return True
-
-    @classmethod
-    def change_item(cls, **kwargs):
-        kwargs['item'].managers.append(kwargs['other'])
-        if kwargs['other'].is_state_disabled():
-            kwargs['other'].set_state_enabled()
-            flask.current_app.send_infomail(
-                'users.enable',
-                account = kwargs['other']
-            )
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User <strong>%(user_id)s</strong> was successfully added as a manager to group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to add user <strong>%(user_id)s</strong> as a manager to group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled adding user <strong>%(user_id)s</strong> as a manager to group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-
-class RemoveManagerView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for removing group managers.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    @classmethod
-    def get_view_name(cls):
-        return 'removemanager'
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return gettext('Remove group manager')
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-rem-manager'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Remove user &quot;%(user_id)s&quot; from group &quot;%(group_id)s&quot; as manager',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @property
-    def dbmodel_other(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['item'].id)
-        )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
-
-    @classmethod
-    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        # Reject item change in case given item is not already manager.
-        if kwargs['other'] not in kwargs['item'].managers:
-            return False
-        return True
-
-    @classmethod
-    def change_item(cls, **kwargs):
-        try:
-            kwargs['item'].managers.remove(kwargs['other'])
-        except ValueError:
-            pass
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User <strong>%(user_id)s</strong> was successfully removed as a manager from group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to remove user <strong>%(user_id)s</strong> as a manager from group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled removing user <strong>%(user_id)s</strong> as a manager from group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['other'])),
-            group_id = flask.escape(str(kwargs['item']))
-        )
-
-
-class EnableView(HTMLMixin, SQLAlchemyMixin, ItemEnableView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for enabling existing groups.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    authorization = [vial.acl.PERMISSION_POWER]
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Enable group &quot;%(item)s&quot;',
-            item = flask.escape(str(kwargs['item']))
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'Group <strong>%(item_id)s</strong> was successfully enabled.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to enable group <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled enabling group <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-
-class DisableView(HTMLMixin, SQLAlchemyMixin, ItemDisableView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for disabling groups.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    authorization = [vial.acl.PERMISSION_POWER]
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Disable group &quot;%(item)s&quot;',
-            item = flask.escape(str(kwargs['item']))
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'Group <strong>%(item_id)s</strong> was successfully disabled.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to disable group <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled disabling group <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-
-class DeleteView(HTMLMixin, SQLAlchemyMixin, ItemDeleteView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for deleting existing groups.
-    """
-
-    methods = ['GET','POST']
-
-    authentication = True
-
-    authorization = [vial.acl.PERMISSION_ADMIN]
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Delete group &quot;%(item)s&quot;',
-            item = flask.escape(str(kwargs['item']))
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'Group <strong>%(item_id)s</strong> was successfully and permanently deleted.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to delete group <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled deleting group <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-
-#-------------------------------------------------------------------------------
-
-
-class GroupsBlueprint(VialBlueprint):
-    """Pluggable module - user groups (*groups*)."""
-
-    @classmethod
-    def get_module_title(cls):
-        return lazy_gettext('Group management')
-
-    def register_app(self, app):
-
-        def _fetch_my_groups():
-            groups = {}
-            for i in list(flask_login.current_user.memberships) + list(flask_login.current_user.managements):
-                groups[str(i)] = i
-            return list(sorted(groups.values(), key = str))
-
-        app.menu_main.add_entry(
-            'view',
-            'admin.{}'.format(BLUEPRINT_NAME),
-            position = 50,
-            view = ListView
-        )
-        app.menu_auth.add_entry(
-            'submenudb',
-            'my_groups',
-            position = 20,
-            title = lazy_gettext('My groups'),
-            resptitle = True,
-            icon = 'module-groups',
-            align_right = True,
-            entry_fetcher = _fetch_my_groups,
-            entry_builder = lambda x, y: vial.menu.EndpointEntry(x, endpoint = 'groups.show', params = {'item': y}, title = x, icon = 'module-groups')
-        )
-
-
-#-------------------------------------------------------------------------------
-
-
-def get_blueprint():
-    """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
-    :py:class:`flask.Blueprint`.
-    """
-
-    hbp = GroupsBlueprint(
-        BLUEPRINT_NAME,
-        __name__,
-        template_folder = 'templates',
-        url_prefix = '/{}'.format(BLUEPRINT_NAME)
-    )
-
-    hbp.register_view_class(ListView,          '/list')
-    hbp.register_view_class(CreateView,        '/create')
-    hbp.register_view_class(ShowView,          '/<int:item_id>/show')
-    hbp.register_view_class(ShowByNameView,    '/<item_id>/show_by_name')
-    hbp.register_view_class(UpdateView,        '/<int:item_id>/update')
-    hbp.register_view_class(AddMemberView,     '/<int:item_id>/add_member/<int:other_id>')
-    hbp.register_view_class(RejectMemberView,  '/<int:item_id>/reject_member/<int:other_id>')
-    hbp.register_view_class(RemoveMemberView,  '/<int:item_id>/remove_member/<int:other_id>')
-    hbp.register_view_class(AddManagerView,    '/<int:item_id>/add_manager/<int:other_id>')
-    hbp.register_view_class(RemoveManagerView, '/<int:item_id>/remove_manager/<int:other_id>')
-    hbp.register_view_class(EnableView,        '/<int:item_id>/enable')
-    hbp.register_view_class(DisableView,       '/<int:item_id>/disable')
-    hbp.register_view_class(DeleteView,        '/<int:item_id>/delete')
-
-    return hbp
diff --git a/lib/vial/blueprints/groups/forms.py b/lib/vial/blueprints/groups/forms.py
deleted file mode 100644
index 53ce5ff20..000000000
--- a/lib/vial/blueprints/groups/forms.py
+++ /dev/null
@@ -1,255 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-This module contains custom group management forms for Hawat.
-"""
-
-
-import wtforms
-from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
-from flask_babel import gettext, lazy_gettext
-
-import vial.db
-import vial.forms
-from vial.forms import get_available_users, get_available_group_sources
-
-def check_parent_not_self(form, field):
-    """
-    Callback for validating that parent group is not self.
-    """
-    if field.data and form.db_item_id == field.data.id:
-        raise wtforms.validators.ValidationError(gettext('You must not select a group as its own parent! Naughty, naughty you!'))
-
-
-def format_select_option_label_user(item):
-    """
-    Format option for selection of user accounts.
-    """
-    return "{} ({})".format(item.fullname, item.login)
-
-
-class BaseGroupForm(vial.forms.BaseItemForm):
-    """
-    Class representing base group form.
-    """
-    description = wtforms.StringField(
-        lazy_gettext('Description:'),
-        validators = [
-            wtforms.validators.DataRequired()
-        ],
-        description = lazy_gettext('Additional and more extensive group description.')
-    )
-    source = wtforms.HiddenField(
-        default = 'manual',
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.Length(min = 3, max = 50)
-        ],
-        description = lazy_gettext('Origin of the group record, whether it was added manually, or via some automated mechanism from data from some third party system.')
-    )
-    members = QuerySelectMultipleField(
-        lazy_gettext('Members:'),
-        query_factory = vial.forms.get_available_users,
-        get_label = format_select_option_label_user,
-        blank_text = lazy_gettext('<< no selection >>'),
-        description = lazy_gettext('List of group members.')
-    )
-    submit = wtforms.SubmitField(
-        lazy_gettext('Submit')
-    )
-    cancel = wtforms.SubmitField(
-        lazy_gettext('Cancel')
-    )
-
-
-class UpdateGroupForm(BaseGroupForm):
-    """
-    Class representing group update form for regular users.
-    """
-
-
-class AdminBaseGroupForm(BaseGroupForm):
-    """
-    Class representing group create form.
-    """
-    enabled = wtforms.RadioField(
-        lazy_gettext('State:'),
-        validators = [
-            wtforms.validators.InputRequired(),
-        ],
-        choices = [
-            (True,  lazy_gettext('Enabled')),
-            (False, lazy_gettext('Disabled'))
-        ],
-        filters = [vial.forms.str_to_bool],
-        coerce = vial.forms.str_to_bool,
-        description = lazy_gettext('Boolean flag whether the group is enabled or disabled. Disabled groups are hidden to the most of the system features.')
-    )
-    managers = QuerySelectMultipleField(
-        lazy_gettext('Managers:'),
-        query_factory = vial.forms.get_available_users,
-        get_label = format_select_option_label_user,
-        blank_text = lazy_gettext('<< no selection >>'),
-        description = lazy_gettext('List of users acting as group managers. These users may change various group settings.')
-    )
-    parent = QuerySelectField(
-        lazy_gettext('Parent group:'),
-        validators = [
-            wtforms.validators.Optional(),
-            check_parent_not_self
-        ],
-        query_factory = vial.forms.get_available_groups,
-        allow_blank = True,
-        blank_text = lazy_gettext('<< no selection >>'),
-        description = lazy_gettext('Parent group for this group. This feature enables the posibility to create structured group hierarchy.')
-    )
-
-
-class AdminCreateGroupForm(AdminBaseGroupForm):
-    """
-    Class representing group create form for administrators.
-    """
-    name = wtforms.StringField(
-        lazy_gettext('Name:'),
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.Length(min = 3, max = 100),
-            vial.forms.check_unique_group
-        ],
-        description = lazy_gettext('System-wide unique name for the group.')
-    )
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        self.db_item_id = None
-
-
-class AdminUpdateGroupForm(AdminBaseGroupForm):
-    """
-    Class representing group update form for administrators.
-    """
-    name = wtforms.StringField(
-        lazy_gettext('Name:'),
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.Length(min = 3, max = 100),
-            vial.forms.check_unique_group
-        ],
-        description = lazy_gettext('System-wide unique name for the group.')
-    )
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        # Store the ID of original item in database to enable the ID uniqueness
-        # check with check_name_uniqueness() validator.
-        self.db_item_id = kwargs['db_item_id']
-
-
-class GroupSearchForm(vial.forms.BaseSearchForm):
-    """
-    Class representing simple user search form.
-    """
-    search = wtforms.StringField(
-        lazy_gettext('Name, description:'),
-        validators = [
-            wtforms.validators.Optional(),
-            wtforms.validators.Length(min = 3, max = 100)
-        ],
-        description = lazy_gettext('Group`s full name or description. Search is performed even in the middle of the strings.')
-    )
-    dt_from = vial.forms.SmartDateTimeField(
-        lazy_gettext('Creation time from:'),
-        validators = [
-            wtforms.validators.Optional()
-        ],
-        description = lazy_gettext('Lower time boundary for item creation time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences.')
-    )
-    dt_to = vial.forms.SmartDateTimeField(
-        lazy_gettext('Creation time to:'),
-        validators = [
-            wtforms.validators.Optional()
-        ],
-        description = lazy_gettext('Upper time boundary for item creation time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences.')
-    )
-
-    state = wtforms.SelectField(
-        lazy_gettext('State:'),
-        validators = [
-            wtforms.validators.Optional(),
-        ],
-        choices = [
-            ('', lazy_gettext('Nothing selected')),
-            ('enabled',  lazy_gettext('Enabled')),
-            ('disabled', lazy_gettext('Disabled'))
-        ],
-        default = '',
-        description = lazy_gettext('Search for groups with particular state.')
-    )
-    source = wtforms.SelectField(
-        lazy_gettext('Record source:'),
-        validators = [
-            wtforms.validators.Optional()
-        ],
-        default = '',
-        description = lazy_gettext('Search for groups coming from particular sources/feeds.')
-    )
-    members = QuerySelectField(
-        lazy_gettext('Group members:'),
-        query_factory = get_available_users,
-        allow_blank = True,
-        description = lazy_gettext('Search for groups with particular members.')
-    )
-    managers = QuerySelectField(
-        lazy_gettext('Group managers:'),
-        query_factory = get_available_users,
-        allow_blank = True,
-        description = lazy_gettext('Search for groups with particular managers.')
-    )
-
-    sortby = wtforms.SelectField(
-        lazy_gettext('Sort by:'),
-        validators = [
-            wtforms.validators.Optional()
-        ],
-        choices = [
-            ('createtime.desc', lazy_gettext('by creation time descending')),
-            ('createtime.asc',  lazy_gettext('by creation time ascending')),
-            ('name.desc', lazy_gettext('by name descending')),
-            ('name.asc',  lazy_gettext('by name ascending'))
-        ],
-        default = 'name.asc'
-    )
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        #
-        # Handle additional custom keywords.
-        #
-
-        # The list of choices for 'roles' attribute comes from outside of the
-        # form to provide as loose tie as possible to the outer application.
-        # Another approach would be to load available choices here with:
-        #
-        #   roles = flask.current_app.config['ROLES']
-        #
-        # That would mean direct dependency on flask.Flask application.
-        source_list = get_available_group_sources()
-        self.source.choices = [('', lazy_gettext('Nothing selected'))] + list(zip(source_list, source_list))
-
-    @staticmethod
-    def is_multivalue(field_name):
-        """
-        Check, if given form field is a multivalue field.
-
-        :param str field_name: Name of the form field.
-        :return: ``True``, if the field can contain multiple values, ``False`` otherwise.
-        :rtype: bool
-        """
-        return False
diff --git a/lib/vial/blueprints/groups/templates/groups/addmember.html b/lib/vial/blueprints/groups/templates/groups/addmember.html
deleted file mode 100644
index 183e476c8..000000000
--- a/lib/vial/blueprints/groups/templates/groups/addmember.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{%- extends "_layout_confirmation.html" %}
-
-{%- block modalcontent %}
-                                <p>
-                                    {{ _('Are you really sure you want to add user') }}
-                                </p>
-                                <p class="lead text-center">
-                                    {{ other_name }}
-                                </p>
-                                <p>
-                                    {{ _('to abuse group') }}
-                                </p>
-                                <p class="lead text-center">
-                                    {{ item_name }}
-                                </p>
-{% endblock modalcontent %}
diff --git a/lib/vial/blueprints/groups/templates/groups/creatupdate.html b/lib/vial/blueprints/groups/templates/groups/creatupdate.html
deleted file mode 100644
index a08a12bd0..000000000
--- a/lib/vial/blueprints/groups/templates/groups/creatupdate.html
+++ /dev/null
@@ -1,32 +0,0 @@
-{%- extends "_layout_creatupdate.html" %}
-
-{%- block itemform_fields %}
-
-    {%- if item_action == 'create' or current_user.has_role('admin') %}
-
-                                {{ macros_form.render_form_item_default(form.name) }}
-
-    {%- elif item_action == 'update' %}
-
-                                {{ macros_form.render_form_item_static(_('Name:'), item.name) }}
-
-    {%- endif %}
-
-                                {{ macros_form.render_form_item_default(form.description) }}
-
-                                <hr>
-
-    {%- if form | attr('enabled') %}
-                                {{ macros_form.render_form_item_radiobutton(form.enabled) }}
-    {%- endif %}
-    {%- if form | attr('members') %}
-                                {{ macros_form.render_form_item_select(form.members) }}
-    {%- endif %}
-    {%- if form | attr('managers') %}
-                                {{ macros_form.render_form_item_select(form.managers) }}
-    {%- endif %}
-    {%- if form | attr('parent') %}
-                                {{ macros_form.render_form_item_select(form.parent) }}
-    {%- endif %}
-
-{%- endblock itemform_fields %}
diff --git a/lib/vial/blueprints/groups/templates/groups/list.html b/lib/vial/blueprints/groups/templates/groups/list.html
deleted file mode 100644
index c23eaecce..000000000
--- a/lib/vial/blueprints/groups/templates/groups/list.html
+++ /dev/null
@@ -1,78 +0,0 @@
-{%- extends "_layout_list.html" %}
-
-{%- block searchformfields %}
-
-                            <div class="row">
-                                <div class="col-sm-4">
-                                    {{ macros_form.render_form_item_default(g.search_form.search) }}
-                                </div>
-                                <div class="col-sm-4">
-                                    {{ macros_form.render_form_item_datetime(g.search_form.dt_from, 'datetimepicker-hm-from') }}
-                                </div>
-                                <div class="col-sm-4">
-                                    {{ macros_form.render_form_item_datetime(g.search_form.dt_to, 'datetimepicker-hm-to') }}
-                                </div>
-                            </div>
-                            <div class="row">
-                                <div class="col-sm-3">
-                                    {{ macros_form.render_form_item_select(g.search_form.state) }}
-                                </div>
-                                <div class="col-sm-3">
-                                    {{ macros_form.render_form_item_select(g.search_form.source) }}
-                                </div>
-                                <div class="col-sm-3">
-                                    {{ macros_form.render_form_item_select(g.search_form.members) }}
-                                </div>
-                                <div class="col-sm-3">
-                                    {{ macros_form.render_form_item_select(g.search_form.managers) }}
-                                </div>
-                            </div>
-
-{%- endblock searchformfields %}
-
-{%- block contentinner %}
-
-                    <table class="table table-bordered table-hover table-striped">
-                        <thead>
-                            <tr>
-                                <th>
-                                    {{ _('Name') }} {{ macros_site.render_sorter(request.endpoint, query_params, 'name') }}
-                                </th>
-                                <th>
-                                    {{ _('Source') }}
-                                </th>
-                                <th>
-                                    {{ _('Description') }}
-                                </th>
-                                <th>
-                                    {{ _('State') }}
-                                </th>
-                                <th data-toggle="tooltip" title="{{ _('Contextual item actions') }}">
-                                    {{ get_icon('actions') }} {{ _('Actions') }}
-                                </th>
-                            </tr>
-                        </thead>
-                        <tbody>
-                            {%- for item in items %}
-                            <tr>
-                                <td>
-                                    {{ item.name | default(_('<< unknown >>'), True) }}
-                                </td>
-                                <td>
-                                    {{ item.source | default(_('<< unknown >>'), True) }}
-                                </td>
-                                <td>
-                                    {{ item.description | default(_('<< unknown >>'), True) | truncate(50) }}
-                                </td>
-                                <td>
-                                    {{ macros_site.render_label_item_state(item.enabled, True) }}
-                                </td>
-                                <td class="column-actions">
-                                    {{ macros_page.render_menu_context_actions(item) }}
-                                </td>
-                            </tr>
-                            {%- endfor %}
-                        </tbody>
-                    </table>
-
-{%- endblock contentinner %}
diff --git a/lib/vial/blueprints/groups/templates/groups/rejectmember.html b/lib/vial/blueprints/groups/templates/groups/rejectmember.html
deleted file mode 100644
index c28c4a8a3..000000000
--- a/lib/vial/blueprints/groups/templates/groups/rejectmember.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{%- extends "_layout_confirmation.html" %}
-
-{%- block modalcontent %}
-                                <p>
-                                    {{ _('Are you really sure you want to reject membership request of user') }}
-                                </p>
-                                <p class="lead text-center">
-                                    {{ other_name }}
-                                </p>
-                                <p>
-                                    {{ _('for abuse group') }}
-                                </p>
-                                <p class="lead text-center">
-                                    {{ item_name }}
-                                </p>
-{% endblock modalcontent %}
diff --git a/lib/vial/blueprints/groups/templates/groups/removemember.html b/lib/vial/blueprints/groups/templates/groups/removemember.html
deleted file mode 100644
index 72d449bea..000000000
--- a/lib/vial/blueprints/groups/templates/groups/removemember.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{%- extends "_layout_confirmation.html" %}
-
-{%- block modalcontent %}
-                                <p>
-                                    {{ _('Are you really sure you want to remove user') }}
-                                </p>
-                                <p class="lead text-center">
-                                    {{ other_name }}
-                                </p>
-                                <p>
-                                    {{ _('from abuse group') }}
-                                </p>
-                                <p class="lead text-center">
-                                    {{ item_name }}
-                                </p>
-{% endblock modalcontent %}
diff --git a/lib/vial/blueprints/groups/templates/groups/show.html b/lib/vial/blueprints/groups/templates/groups/show.html
deleted file mode 100644
index fdf42a305..000000000
--- a/lib/vial/blueprints/groups/templates/groups/show.html
+++ /dev/null
@@ -1,187 +0,0 @@
-{%- extends "_layout.html" %}
-
-{%- block content %}
-
-            <div class="row">
-                <div class="col-lg-12">
-                    {{ macros_page.render_breadcrumbs(item) }}
-
-                    <h2>{{ vial_current_view.get_view_title() }}</h2>
-                    <hr>
-                    <h3>{{ item.name }}{% if item.description %} <small>{{ item.description }}</small>{% endif %}</h3>
-                    <div class="pull-right">
-                        {{ macros_page.render_menu_actions(item) }}
-
-                    </div>
-                    <p>
-                        <small>
-                            <strong>{{ _('Group created') }}:</strong> {{ babel_format_datetime(item.createtime) }} ({{ _('%(delta)s ago', delta = babel_format_timedelta(current_datetime_utc - item.createtime)) }})
-                        </small>
-                    </p>
-                    <br>
-
-                    <!-- Nav tabs -->
-                    <ul class="nav nav-tabs" role="tablist">
-                        <li role="presentation" class="active">
-                            <a href="#tab-general" aria-controls="tab-general" role="tab" data-toggle="tab">
-                                <strong>{{ get_icon('alert-info') }} {{ _('General information') }}</strong>
-                            </a>
-                        </li>
-                        {%- if can_access_endpoint('groups.update', item) %}
-                        <li role="presentation">
-                            <a href="#tab-changelog" aria-controls="tab-changelog" role="tab" data-toggle="tab">
-                                <strong>{{ get_icon('module-changelogs') }} {{ _('Changelogs') }}</strong> <span class="badge">{{ item_changelog | length }}</span>
-                            </a>
-                        </li>
-                        {%- endif %}
-                    </ul>
-
-                    <!-- Tab panes -->
-                    <div class="tab-content">
-
-                        <div role="tabpanel" class="tab-pane fade in active" id="tab-general">
-                            <br>
-                            <div class="row">
-                                <div class="col-md-4">
-                                    <h4>{{ _('Metadata') }}</h4>
-                                    <table class="table table-striped">
-                                        <tbody>
-                                            <tr>
-                                                <th>
-                                                    {{ _('Parent group') }}:
-                                                </th>
-                                                <td>
-                                                    {%- if item.parent %}
-                                                    <a data-toggle="tooltip" href="{{ url_for('groups.show', item_id = item.parent.id ) }}" title="{{ _('View details of group &quot;%(item)s&quot;', item = item.parent.name) }}">
-                                                        {{ item.parent.name }}
-                                                    </a>
-                                                    {%- else %}
-                                                    {{ _('<< none >>') }}
-                                                    {%- endif %}
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th>
-                                                    {{ _('State') }}:
-                                                </th>
-                                                <td>
-                                                    {{ macros_site.render_label_item_state(item.enabled, True) }}
-                                                </td>
-                                            </tr>
-                                        </tbody>
-                                    </table>
-                                </div>
-
-                                <div class="col-md-4">
-                                    {%- if item.members_wanted %}
-                                    <h4>
-                                        {{ _('Membership requests') }} <span class="badge">{{ item.members_wanted | length }}</span>
-                                    </h4>
-                                    <table class="table table-striped">
-                                        <tbody>
-                                            {%- for subitem in item.members_wanted %}
-                                            <tr>
-                                                <td{%- if not subitem.enabled %} class="text-muted"{% endif %}>
-                                                    {{ subitem.login }}
-                                                </td>
-                                                <td{%- if not subitem.enabled %} class="text-muted"{% endif %}>
-                                                    {{ subitem.fullname }}
-                                                </td>
-                                                <td>
-                                                    {{ macros_page.render_menu_context_actions(subitem, context_action_menu_users, kwargs = {'other': item}) }}
-                                                </td>
-                                            </tr>
-                                            {%- endfor %}
-                                        </tbody>
-                                    </table>
-                                    {%- endif %}
-                                    <h4>
-                                        {{ _('Members') }} <span class="badge">{{ item.members | length }}</span>
-                                    </h4>
-                                    {%- if item.members %}
-                                    <table class="table table-striped">
-                                        <tbody>
-                                            {%- for subitem in item.members %}
-                                            <tr>
-                                                <td{%- if not subitem.enabled %} class="text-muted"{% endif %}>
-                                                    {{ subitem.login }}
-                                                </td>
-                                                <td{%- if not subitem.enabled %} class="text-muted"{% endif %}>
-                                                    {{ subitem.fullname }}
-                                                </td>
-                                                <td>
-                                                    {{ macros_page.render_menu_context_actions(subitem, context_action_menu_users, kwargs = {'other': item}) }}
-                                                </td>
-                                            </tr>
-                                            {%- endfor %}
-                                        </tbody>
-                                    </table>
-                                    {%- else %}
-                                    {%- call macros_site.render_alert('info', False) %}
-                                    {{ _('This group does not have any members defined at the moment.') }}
-                                    {%- endcall %}
-                                    {%- endif %}
-                                </div>
-
-                                <div class="col-md-4">
-                                    <h4>{{ _('Managers') }} <span class="badge">{{ item.managers | length }}</span></h4>
-                                    {%- if item.managers %}
-                                    <table class="table table-striped">
-                                        <tbody>
-                                            {%- for subitem in item.managers %}
-                                            <tr>
-                                                <td>
-                                                    {{ subitem.login }}
-                                                </td>
-                                                <td>
-                                                    {{ subitem.fullname }}
-                                                </td>
-                                                <td>
-                                                    {{ macros_page.render_menu_context_actions(subitem, context_action_menu_users, kwargs = {'other': item}) }}
-                                                </td>
-                                            </tr>
-                                            {%- endfor %}
-                                        </tbody>
-                                    </table>
-                                    {%- else %}
-                                    {%- call macros_site.render_alert('info', False) %}
-                                    {{ _('This group does not have any managers defined at the moment.') }}
-                                    {%- endcall %}
-                                    {%- endif %}
-                                </div>
-
-                            </div>
-                        </div>
-
-                        {%- if can_access_endpoint('groups.update', item) %}
-                        <div role="tabpanel" class="tab-pane fade" id="tab-changelog">
-                            <br>
-                            {%- if item_changelog %}
-                            {{ macros_page.render_changelog_records(item_changelog, context_action_menu_changelogs) }}
-                            <p>
-                                <small>
-                                    {{ _('Displaying only latest %(count)s changelogs', count = 100) }}
-                                </small>
-                            </p>
-                            {%- else %}
-                            {%- call macros_site.render_alert('info', False) %}
-                            {{ _('This group does not have any changelog records at the moment.') }}
-                            {%- endcall %}
-                            {%- endif %}
-                        </div>
-                        {%- endif %}
-
-                    </div>
-
-                </div><!-- /.col-lg-12 -->
-            </div><!-- /.row -->
-
-    {%- if permission_can('developer') %}
-
-            <hr>
-
-            {{ macros_site.render_raw_item_view(item) }}
-
-    {%- endif %}
-
-{%- endblock content %}
diff --git a/lib/vial/blueprints/groups/test/__init__.py b/lib/vial/blueprints/groups/test/__init__.py
deleted file mode 100644
index ea8f3f694..000000000
--- a/lib/vial/blueprints/groups/test/__init__.py
+++ /dev/null
@@ -1,541 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-Unit tests for :py:mod:`vial.blueprints.groups`.
-"""
-
-
-import sys
-import unittest
-
-import hawat.const
-import vial.db
-import vial.test
-import vial.test.fixtures
-import vial.test.runner
-from vial.test import VialTestCase, ItemCreateVialTestCase
-from vial.test.runner import TestRunnerMixin
-
-
-_IS_NOSE = sys.argv[0].endswith('nosetests')
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class GroupsListTestCase(TestRunnerMixin, VialTestCase):
-    """Class for testing ``groups.list`` endpoint."""
-
-    def _attempt_fail(self):
-        self.assertGetURL(
-            '/groups/list',
-            403
-        )
-
-    def _attempt_succeed(self):
-        self.assertGetURL(
-            '/groups/list',
-            200,
-            [
-                b'View details of group &quot;DEMO_GROUP_A&quot;',
-                b'View details of group &quot;DEMO_GROUP_B&quot;',
-            ]
-        )
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """Test access as user ``user``."""
-        self._attempt_fail()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_02_as_developer(self):
-        """Test access as user ``developer``."""
-        self._attempt_fail()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_03_as_maintainer(self):
-        """Test access as user ``maintainer``."""
-        self._attempt_succeed()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_04_as_admin(self):
-        """Test access as user ``admin``."""
-        self._attempt_succeed()
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class GroupsShowTestCase(TestRunnerMixin, VialTestCase):
-    """Base class for testing ``groups.show`` and ``groups.show_by_name`` endpoints."""
-
-    def _attempt_fail(self, gname):
-        gid = self.group_id(gname, with_app_ctx = True)
-        self.assertGetURL(
-            '/groups/{}/show'.format(gid),
-            403
-        )
-        self.assertGetURL(
-            '/groups/{}/show_by_name'.format(gname),
-            403
-        )
-
-    def _attempt_succeed(self, gname):
-        gid = self.group_id(gname, with_app_ctx = True)
-        self.assertGetURL(
-            '/groups/{}/show'.format(gid),
-            200,
-            [
-                '<h3>{} '.format(gname).encode('utf8'),
-                b'<strong>Group created:</strong>'
-            ]
-        )
-        self.assertGetURL(
-            '/groups/{}/show_by_name'.format(gname),
-            200,
-            [
-                '<h3>{} '.format(gname).encode('utf8'),
-                b'<strong>Group created:</strong>'
-            ]
-        )
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """
-        Test access as user 'user'.
-
-        Only power user is able to view all available groups.
-        """
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_02_as_developer(self):
-        """
-        Test access as user 'developer'.
-
-        Only power user is able to view all available groups.
-        """
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_03_as_maintainer(self):
-        """
-        Test access as user 'maintainer'.
-
-        Only power user is able to view all available groups.
-        """
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_04_as_admin(self):
-        """
-        Test access as user 'admin'.
-
-        Only power user is able to view all available groups.
-        """
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class GroupsCreateTestCase(TestRunnerMixin, ItemCreateVialTestCase):
-    """Class for testing ``groups.create`` endpoint."""
-
-    group_data_fixture = [
-        ('name', 'TEST_GROUP'),
-        ('description', 'Test group for unit testing purposes.'),
-        ('enabled', True)
-    ]
-
-    def _attempt_fail(self):
-        self.assertGetURL(
-            '/groups/create',
-            403
-        )
-
-    def _attempt_succeed(self):
-        self.assertCreate(
-            self.group_model(),
-            '/groups/create',
-            self.group_data_fixture,
-            [
-                b'Group <strong>TEST_GROUP</strong> was successfully created.'
-            ]
-        )
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """Test access as user 'user'."""
-        self._attempt_fail()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_02_as_developer(self):
-        """Test access as user 'developer'."""
-        self._attempt_fail()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_03_as_maintainer(self):
-        """Test access as user 'maintainer'."""
-        self._attempt_succeed()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_04_as_admin(self):
-        """Test access as user 'admin'."""
-        self._attempt_succeed()
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class GroupsUpdateTestCase(TestRunnerMixin, VialTestCase):
-    """Class for testing ``groups.update`` endpoint."""
-
-    def _attempt_fail(self, gname):
-        gid = self.group_id(gname, with_app_ctx = True)
-        self.assertGetURL(
-            '/groups/{}/update'.format(gid),
-            403
-        )
-
-    def _attempt_succeed(self, gname):
-        gid = self.group_id(gname, with_app_ctx = True)
-        self.assertGetURL(
-            '/groups/{}/update'.format(gid),
-            200,
-            [
-                b'Update group details'
-            ]
-        )
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """Test access as user 'user'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_04_as_developer(self):
-        """Test access as user 'developer'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_05_as_maintainer(self):
-        """Test access as user 'maintainer'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_06_as_admin(self):
-        """Test access as user 'admin'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class GroupsEnableDisableTestCase(TestRunnerMixin, VialTestCase):
-    """Class for testing ``groups.enable`` and ``groups.disable`` endpoint."""
-
-    def _attempt_fail(self, gname):
-        gid = self.group_id(gname, with_app_ctx = True)
-        self.assertGetURL(
-            '/groups/{}/disable'.format(gid),
-            403
-        )
-        self.assertGetURL(
-            '/groups/{}/enable'.format(gid),
-            403
-        )
-
-    def _attempt_succeed(self, gname):
-        gid = self.group_id(gname, with_app_ctx = True)
-        self.assertGetURL(
-            '/groups/{}/disable'.format(gid),
-            200,
-            [
-                b'Are you really sure you want to disable following item:'
-            ]
-        )
-        self.assertPostURL(
-            '/groups/{}/disable'.format(gid),
-            {
-                'submit': 'Confirm'
-            },
-            200,
-            [
-                b'was successfully disabled.'
-            ]
-        )
-        self.assertGetURL(
-            '/groups/{}/enable'.format(gid),
-            200,
-            [
-                b'Are you really sure you want to enable following item:'
-            ]
-        )
-        self.assertPostURL(
-            '/groups/{}/enable'.format(gid),
-            {
-                'submit': 'Confirm'
-            },
-            200,
-            [
-                b'was successfully enabled.'
-            ]
-        )
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """Test access as user 'user'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_02_as_developer(self):
-        """Test access as user 'developer'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_03_as_maintainer(self):
-        """Test access as user 'maintainer'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_04_as_admin(self):
-        """Test access as user 'admin'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class GroupsAddRemRejMemberTestCase(TestRunnerMixin, VialTestCase):
-    """Class for testing ``groups.add_member``, ``groups.reject_member`` and ``groups.remove_member`` endpoint."""
-
-    def _attempt_fail(self, uname, gname):
-        with self.app.app_context():
-            uid = self.user_id(uname)
-            gid = self.group_id(gname)
-        self.assertGetURL(
-            '/groups/{}/remove_member/{}'.format(gid, uid),
-            403
-        )
-        self.assertGetURL(
-            '/groups/{}/reject_member/{}'.format(gid, uid),
-            403
-        )
-        self.assertGetURL(
-            '/groups/{}/add_member/{}'.format(gid, uid),
-            403
-        )
-
-    def _attempt_succeed(self, uname, gname, print_response = False):
-        # Additional test preparations.
-        with self.app.app_context():
-            uid = self.user_id(uname)
-            gid = self.group_id(gname)
-            self.user_enabled(uname, False)
-        self.mailbox_monitoring('on')
-
-        #
-        # First check the removal of existing membership.
-        #
-        self.assertGetURL(
-            '/groups/{}/remove_member/{}'.format(gid, uid),
-            200,
-            [
-                b'Are you really sure you want to remove user'
-            ],
-            print_response
-        )
-        self.assertPostURL(
-            '/groups/{}/remove_member/{}'.format(gid, uid),
-            {
-                'submit': 'Confirm'
-            },
-            200,
-            [
-                b'was successfully removed as a member from group'
-            ],
-            print_response
-        )
-
-        #
-        # Add user back to group.
-        #
-        self.assertGetURL(
-            '/groups/{}/add_member/{}'.format(gid, uid),
-            200,
-            [
-                b'Are you really sure you want to add user'
-            ],
-            print_response
-        )
-        self.assertPostURL(
-            '/groups/{}/add_member/{}'.format(gid, uid),
-            {
-                'submit': 'Confirm'
-            },
-            200,
-            [
-                b'was successfully added as a member to group'
-            ],
-            print_response
-        )
-        self.assertMailbox(
-            {
-                'subject': [
-                    '[{}] Account activation - {}'.format(self.app.config['APPLICATION_NAME'], uname)
-                ],
-                'sender': [
-                    'root@unittest'
-                ],
-                'recipients': [
-                    ['{}@bogus-domain.org'.format(uname)]
-                ],
-                'cc': [[]],
-                'bcc': [['admin@unittest']]
-            }
-        )
-        self.mailbox_clear()
-
-        # Additional test preparations.
-        with self.app.app_context():
-            uobj = self.user_get(uname)
-            gobj = self.group_get(gname)
-            uid = uobj.id
-            gid = gobj.id
-            uobj.enabled = False
-            uobj.memberships.remove(gobj)
-            uobj.memberships_wanted.append(gobj)
-            self.user_save(uobj)
-
-        #
-        # Check membership request rejection feature.
-        #
-        self.assertGetURL(
-            '/groups/{}/reject_member/{}'.format(gid, uid),
-            200,
-            [
-                b'Are you really sure you want to reject membership request of user'
-            ],
-            print_response
-        )
-        self.assertPostURL(
-            '/groups/{}/reject_member/{}'.format(gid, uid),
-            {
-                'submit': 'Confirm'
-            },
-            200,
-            [
-                b'was successfully rejected.'
-            ],
-            print_response
-        )
-        self.mailbox_monitoring('off')
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """Test access as user 'user'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_MAINTAINER,
-                hawat.const.ROLE_ADMIN
-            ):
-            self._attempt_fail(uname, vial.test.fixtures.DEMO_GROUP_A)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_02_as_developer(self):
-        """Test access as user 'developer'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_MAINTAINER,
-                hawat.const.ROLE_ADMIN
-            ):
-            self._attempt_succeed(uname, vial.test.fixtures.DEMO_GROUP_A)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_03_as_maintainer(self):
-        """Test access as user 'maintainer'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_ADMIN
-            ):
-            self._attempt_succeed(uname, vial.test.fixtures.DEMO_GROUP_A)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_04_as_admin(self):
-        """Test access as user 'admin'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_MAINTAINER
-            ):
-            self._attempt_succeed(uname, vial.test.fixtures.DEMO_GROUP_A)
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class GroupsDeleteTestCase(TestRunnerMixin, VialTestCase):
-    """Class for testing ``groups.delete`` endpoint."""
-
-    def _attempt_fail(self, gname):
-        gid = self.group_id(gname, with_app_ctx = True)
-        self.assertGetURL(
-            '/groups/{}/delete'.format(gid),
-            403
-        )
-
-    def _attempt_succeed(self, gname):
-        gid = self.group_id(gname, with_app_ctx = True)
-        self.assertGetURL(
-            '/groups/{}/delete'.format(gid),
-            200,
-            [
-                b'Are you really sure you want to permanently remove following item:'
-            ]
-        )
-        self.assertPostURL(
-            '/groups/{}/delete'.format(gid),
-            {
-                'submit': 'Confirm'
-            },
-            200,
-            [
-                b'was successfully and permanently deleted.'
-            ]
-        )
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """Test access as user 'user'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_02_as_developer(self):
-        """Test access as user 'developer'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_03_as_maintainer(self):
-        """Test access as user 'maintainer'."""
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_fail(vial.test.fixtures.DEMO_GROUP_B)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_04_as_admin(self):
-        """Test access as user 'admin'."""
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_A)
-        self._attempt_succeed(vial.test.fixtures.DEMO_GROUP_B)
-
-
-#-------------------------------------------------------------------------------
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/lib/vial/blueprints/home/__init__.py b/lib/vial/blueprints/home/__init__.py
deleted file mode 100644
index 10ecd5656..000000000
--- a/lib/vial/blueprints/home/__init__.py
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-"""
-This pluggable module provides default home page.
-
-
-Provided endpoints
-------------------
-
-``/``
-    Page providing home page.
-
-    * *Authentication:* no authentication
-    * *Methods:* ``GET``
-"""
-
-
-from flask_babel import lazy_gettext
-
-from vial.app import VialBlueprint
-from vial.view import SimpleView
-from vial.view.mixin import HTMLMixin
-
-
-BLUEPRINT_NAME = 'home'
-"""Name of the blueprint as module global constant."""
-
-
-class IndexView(HTMLMixin, SimpleView):
-    """
-    View presenting home page.
-    """
-    methods = ['GET','POST']
-
-    @classmethod
-    def get_view_name(cls):
-        return 'index'
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'module-home'
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('Welcome!')
-
-    @classmethod
-    def get_menu_title(cls, **kwargs):
-        return lazy_gettext('Home')
-
-
-#-------------------------------------------------------------------------------
-
-
-class HomeBlueprint(VialBlueprint):
-    """Pluggable module - home page (*home*)."""
-
-    @classmethod
-    def get_module_title(cls):
-        return lazy_gettext('Home page')
-
-
-#-------------------------------------------------------------------------------
-
-
-def get_blueprint():
-    """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
-    :py:class:`flask.Blueprint`.
-    """
-
-    hbp = HomeBlueprint(
-        BLUEPRINT_NAME,
-        __name__,
-        template_folder = 'templates'
-    )
-
-    hbp.register_view_class(IndexView, '/')
-
-    return hbp
diff --git a/lib/vial/blueprints/home/templates/home/index.html b/lib/vial/blueprints/home/templates/home/index.html
deleted file mode 100644
index e44993332..000000000
--- a/lib/vial/blueprints/home/templates/home/index.html
+++ /dev/null
@@ -1,39 +0,0 @@
-{% extends "_layout.html" %}
-
-{% block title %}{{ vial_current_view.get_view_title() }}{% endblock %}
-
-{% block content %}
-<div class="row">
-    <div class="col-lg-12">
-        <div class="jumbotron" style="margin-top: 1em;">
-            <h1 class="text-center">
-                {{ vial_current_view.get_view_title() }}
-            </h1>
-            <hr>
-            <br>
-            <img src="{{ url_for('design.static', filename='images/app-logo.png') }}" class="welcome-logo img-responsive center-block">
-            {% if not current_user.is_authenticated %}
-            <br>
-            <br>
-            <p class="lead text-center">
-                {{ _('You have to be authenticated to use most features provided by this application.') }}
-            </p>
-            <br>
-    {%- set endpoint_register = get_endpoint_class('auth.register') %}
-    {%- set endpoint_login = get_endpoint_class('auth.login') %}
-            <div class="text-center">
-                <div class="btn-group" role="group">
-                    <a class="btn btn-default btn-lg" href="{{ endpoint_register.get_view_url() }}" role="button">
-                        {{ get_icon(endpoint_register.get_view_icon()) }} {{ endpoint_register.get_menu_title() }}
-                    </a>
-                    <a class="btn btn-primary btn-lg" href="{{ endpoint_login.get_view_url() }}" role="button">
-                        {{ get_icon(endpoint_login.get_view_icon()) }} {{ endpoint_login.get_menu_title() }}
-                    </a>
-                </div>
-            </div>
-            {% endif %}
-        </div>
-    </div>
-    <!-- /.col-lg-12 -->
-</div>
-{% endblock %}
diff --git a/lib/vial/blueprints/home/test/__init__.py b/lib/vial/blueprints/home/test/__init__.py
deleted file mode 100644
index 20b47a395..000000000
--- a/lib/vial/blueprints/home/test/__init__.py
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-Unit tests for :py:mod:`vial.blueprints.home`.
-"""
-
-
-import sys
-import unittest
-
-import hawat.const
-import vial.db
-import vial.test
-import vial.test.runner
-from vial.test import VialTestCase
-from vial.test.runner import TestRunnerMixin
-
-
-_IS_NOSE = sys.argv[0].endswith('nosetests')
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class HomeTestCase(TestRunnerMixin, VialTestCase):
-    """
-    Class for testing ``home.index`` endpoint.
-    """
-
-    def _attempt_succeed(self):
-        self.assertGetURL(
-            '/',
-            200,
-            [
-                b'Welcome!'
-            ]
-        )
-
-    def test_01_as_anonymous(self):
-        """Test access as anonymous user."""
-        self._attempt_succeed()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_02_as_user(self):
-        """Test access as user ``user``."""
-        self._attempt_succeed()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_03_as_developer(self):
-        """Test access as user ``developer``."""
-        self._attempt_succeed()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_04_as_maintainer(self):
-        """Test access as user ``maintainer``."""
-        self._attempt_succeed()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_05_as_admin(self):
-        """Test access as user ``admin``."""
-        self._attempt_succeed()
-
-
-#-------------------------------------------------------------------------------
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/lib/vial/blueprints/users/__init__.py b/lib/vial/blueprints/users/__init__.py
deleted file mode 100644
index 5df50e104..000000000
--- a/lib/vial/blueprints/users/__init__.py
+++ /dev/null
@@ -1,1230 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-This file contains pluggable module for Vial application containing features
-related to user account management. These features include:
-
-* general user account listing
-* detailed user account view
-* creating new user accounts
-* updating existing user accounts
-* deleting existing user accounts
-* enabling existing user accounts
-* disabling existing user accounts
-* adding group memberships
-* removing group memberships
-* rejecting group membership requests
-"""
-
-
-import flask
-import flask_login
-import flask_principal
-import flask_mail
-from flask_babel import gettext, lazy_gettext, force_locale
-from sqlalchemy import or_
-
-import hawat.const
-import vial.acl
-from vial.app import VialBlueprint
-from vial.view import ItemListView, ItemShowView, ItemCreateView, ItemUpdateView, ItemDeleteView, ItemEnableView, ItemDisableView, ItemObjectRelationView
-from vial.view.mixin import HTMLMixin, SQLAlchemyMixin
-from vial.blueprints.users.forms import CreateUserAccountForm, UpdateUserAccountForm, AdminUpdateUserAccountForm, UserSearchForm
-
-
-BLUEPRINT_NAME = 'users'
-"""Name of the blueprint as module global constant."""
-
-
-class ListView(HTMLMixin, SQLAlchemyMixin, ItemListView):
-    """
-    General user account listing.
-    """
-    methods = ['GET']
-
-    authentication = True
-
-    authorization = [vial.acl.PERMISSION_POWER]
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('User management')
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @classmethod
-    def get_action_menu(cls):
-        action_menu = vial.menu.Menu()
-        action_menu.add_entry(
-            'endpoint',
-            'create',
-            endpoint = 'users.create',
-            resptitle = True
-        )
-        return action_menu
-
-    @classmethod
-    def get_context_action_menu(cls):
-        action_menu = vial.menu.Menu()
-        action_menu.add_entry(
-            'endpoint',
-            'show',
-            endpoint = 'users.show',
-            hidetitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'update',
-            endpoint = 'users.update',
-            hidetitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'disable',
-            endpoint = 'users.disable',
-            hidetitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'enable',
-            endpoint = 'users.enable',
-            hidetitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'delete',
-            endpoint = 'users.delete',
-            hidetitle = True
-        )
-        return action_menu
-
-    @staticmethod
-    def get_search_form(request_args):
-        """
-        Must return instance of :py:mod:`flask_wtf.FlaskForm` appropriate for
-        searching given type of items.
-        """
-        roles = list(zip(flask.current_app.config['ROLES'], flask.current_app.config['ROLES']))
-        return UserSearchForm(
-            request_args,
-            meta = {'csrf': False},
-            choices_roles = roles
-        )
-
-    @staticmethod
-    def build_query(query, model, form_args):
-        # Adjust query based on text search string.
-        if 'search' in form_args and form_args['search']:
-            query = query\
-                .filter(
-                    or_(
-                        model.login.like('%{}%'.format(form_args['search'])),
-                        model.fullname.like('%{}%'.format(form_args['search'])),
-                        model.email.like('%{}%'.format(form_args['search'])),
-                    )
-                )
-        # Adjust query based on lower time boudary selection.
-        if 'dt_from' in form_args and form_args['dt_from']:
-            query = query.filter(model.createtime >= form_args['dt_from'])
-        # Adjust query based on upper time boudary selection.
-        if 'dt_to' in form_args and form_args['dt_to']:
-            query = query.filter(model.createtime <= form_args['dt_to'])
-        # Adjust query based on user state selection.
-        if 'state' in form_args and form_args['state']:
-            if form_args['state'] == 'enabled':
-                query = query.filter(model.enabled == True)
-            elif form_args['state'] == 'disabled':
-                query = query.filter(model.enabled == False)
-        # Adjust query based on user role selection.
-        if 'role' in form_args and form_args['role']:
-            if form_args['role'] == hawat.const.NO_ROLE:
-                query = query.filter(model.roles == [])
-            else:
-                query = query.filter(model.roles.any(form_args['role']))
-        # Adjust query based on user membership selection.
-        if 'membership' in form_args and form_args['membership']:
-            query = query\
-                .join(model.memberships)\
-                .filter(model.memberships.any(id = form_args['membership'].id))
-        # Adjust query based on user management selection.
-        if 'management' in form_args and form_args['management']:
-            query = query\
-                .join(model.managements)\
-                .filter(model.managements.any(id = form_args['management'].id))
-        if 'sortby' in form_args and form_args['sortby']:
-            sortmap = {
-                'createtime.desc': lambda x, y: x.order_by(y.createtime.desc()),
-                'createtime.asc': lambda x, y: x.order_by(y.createtime.asc()),
-                'login.desc': lambda x, y: x.order_by(y.login.desc()),
-                'login.asc': lambda x, y: x.order_by(y.login.asc()),
-                'fullname.desc': lambda x, y: x.order_by(y.fullname.desc()),
-                'fullname.asc': lambda x, y: x.order_by(y.fullname.asc()),
-                'email.desc': lambda x, y: x.order_by(y.email.desc()),
-                'email.asc': lambda x, y: x.order_by(y.email.asc()),
-                'logintime.desc': lambda x, y: x.order_by(y.logintime.desc()),
-                'logintime.asc': lambda x, y: x.order_by(y.logintime.asc()),
-            }
-            query = sortmap[form_args['sortby']](query, model)
-        return query
-
-
-class ShowView(HTMLMixin, SQLAlchemyMixin, ItemShowView):
-    """
-    Detailed user account view.
-    """
-    methods = ['GET']
-
-    authentication = True
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-show-user'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Show details of user account &quot;%(item)s&quot;',
-            item = flask.escape(kwargs['item'].login)
-        )
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('Show user account details')
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        # Each user must be able to view his/her account.
-        permission_me = flask_principal.Permission(
-            flask_principal.UserNeed(kwargs['item'].id)
-        )
-        # Managers of the groups the user is member of may view his/her account.
-        needs = [vial.acl.ManagementNeed(x.id) for x in kwargs['item'].memberships]
-        permission_mngr = flask_principal.Permission(*needs)
-        return vial.acl.PERMISSION_POWER.can() or permission_mngr.can() or permission_me.can()
-
-    @classmethod
-    def get_action_menu(cls):
-        action_menu = vial.menu.Menu()
-        action_menu.add_entry(
-            'endpoint',
-            'update',
-            endpoint = 'users.update',
-            resptitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'disable',
-            endpoint = 'users.disable',
-            resptitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'enable',
-            endpoint = 'users.enable',
-            resptitle = True
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'delete',
-            endpoint = 'users.delete',
-            resptitle = True
-        )
-        return action_menu
-
-    def do_before_response(self, **kwargs):  # pylint: disable=locally-disabled,no-self-use,unused-argument
-        item = self.response_context['item']
-        action_menu = vial.menu.Menu()
-        action_menu.add_entry(
-            'endpoint',
-            'show',
-            endpoint = 'groups.show',
-            hidetitle = True
-        )
-        action_menu.add_entry(
-            'submenu',
-            'more',
-            align_right = True,
-            legend = gettext('More actions')
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.add_member',
-            endpoint = 'groups.addmember'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.reject_member',
-            endpoint = 'groups.rejectmember'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.remove_member',
-            endpoint = 'groups.removemember'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.add_manager',
-            endpoint = 'groups.addmanager'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.remove_manager',
-            endpoint = 'groups.removemanager'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.enable',
-            endpoint = 'groups.enable'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.disable',
-            endpoint = 'groups.disable'
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'more.update',
-            endpoint = 'groups.update'
-        )
-        self.response_context.update(
-            context_action_menu_groups = action_menu
-        )
-
-        if self.has_endpoint('changelogs.search'):
-            self.response_context.update(
-                context_action_menu_changelogs = self.get_endpoint_class(
-                    'changelogs.search'
-                ).get_context_action_menu()
-            )
-
-            if self.can_access_endpoint('users.update', item = item) and self.has_endpoint('changelogs.search'):
-                item_changelog_model = self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-                item_changelog = self.dbsession.query(item_changelog_model).\
-                    filter(item_changelog_model.model == item.__class__.__name__).\
-                    filter(item_changelog_model.model_id == item.id).\
-                    order_by(item_changelog_model.createtime.desc()).\
-                    limit(100).\
-                    all()
-                self.response_context.update(
-                    item_changelog = item_changelog
-                )
-
-                user_changelog = self.dbsession.query(item_changelog_model).\
-                    filter(item_changelog_model.author_id == item.id).\
-                    order_by(item_changelog_model.createtime.desc()).\
-                    limit(100).\
-                    all()
-                self.response_context.update(
-                    user_changelog = user_changelog
-                )
-
-
-class MeView(ShowView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    Detailed user account view for currently logged-in user (profile page).
-    """
-    methods = ['GET']
-
-    authentication = True
-
-    authorization = [vial.acl.PERMISSION_ANY]
-
-    @classmethod
-    def get_view_name(cls):
-        return 'me'
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'profile'
-
-    @classmethod
-    def get_menu_title(cls, **kwargs):
-        return lazy_gettext('My account')
-
-    @classmethod
-    def get_view_url(cls, **kwargs):
-        return flask.url_for(cls.get_view_endpoint())
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('My user account')
-
-    @classmethod
-    def get_view_template(cls):
-        return '{}/show.html'.format(BLUEPRINT_NAME)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        return True
-
-    @classmethod
-    def get_breadcrumbs_menu(cls):  # pylint: disable=locally-disabled,unused-argument
-        action_menu = vial.menu.Menu()
-        action_menu.add_entry(
-            'endpoint',
-            'home',
-            endpoint = flask.current_app.config['ENDPOINT_HOME']
-        )
-        action_menu.add_entry(
-            'endpoint',
-            'show',
-            endpoint = '{}.me'.format(cls.module_name)
-        )
-        return action_menu
-
-    def dispatch_request(self):  # pylint: disable=locally-disabled,arguments-differ
-        """
-        Mandatory interface required by the :py:func:`flask.views.View.dispatch_request`.
-        Will be called by the *Flask* framework to service the request.
-
-        Single item with given unique identifier will be retrieved from database
-        and injected into template to be displayed to the user.
-        """
-        item_id = flask_login.current_user.get_id()
-        item = self.dbquery().filter(self.dbmodel.id == item_id).first()
-        if not item:
-            self.abort(404)
-
-        self.response_context.update(
-            item_id = item_id,
-            item = item,
-            breadcrumbs_menu = self.get_breadcrumbs_menu(),
-            action_menu = self.get_action_menu()
-        )
-
-        self.do_before_response()
-
-        return self.generate_response()
-
-
-class CreateView(HTMLMixin, SQLAlchemyMixin, ItemCreateView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for creating new user accounts.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    authorization = [vial.acl.PERMISSION_POWER]
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-create-user'
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('Create new user account')
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User account <strong>%(item_id)s</strong> was successfully created.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext('Unable to create new user account.')
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext('Canceled creating new user account.')
-
-    @staticmethod
-    def get_item_form(item):
-        #
-        # Inject list of choices for supported locales and roles. Another approach
-        # would be to let the form get the list on its own, however that would create
-        # dependency on application object.
-        #
-        roles = list(zip(flask.current_app.config['ROLES'], flask.current_app.config['ROLES']))
-        locales = list(flask.current_app.config['SUPPORTED_LOCALES'].items())
-
-        return CreateUserAccountForm(
-            choices_roles = roles,
-            choices_locales = locales
-        )
-
-
-class UpdateView(HTMLMixin, SQLAlchemyMixin, ItemUpdateView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for updating existing user accounts.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-update-user'
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return lazy_gettext('Update user account details')
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Update details of user account &quot;%(item)s&quot;',
-            item = flask.escape(kwargs['item'].login)
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        permission_me = flask_principal.Permission(
-            flask_principal.UserNeed(kwargs['item'].id)
-        )
-        return vial.acl.PERMISSION_ADMIN.can() or permission_me.can()
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User account <strong>%(item_id)s</strong> was successfully updated.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to update user account <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled updating user account <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_item_form(item):
-        #
-        # Inject list of choices for supported locales and roles. Another approach
-        # would be to let the form get the list on its own, however that would create
-        # dependency on application object.
-        #
-        roles = list(zip(flask.current_app.config['ROLES'], flask.current_app.config['ROLES']))
-        locales = list(flask.current_app.config['SUPPORTED_LOCALES'].items())
-
-        admin = flask_login.current_user.has_role('admin')
-        if not admin:
-            form = UpdateUserAccountForm(
-                choices_roles = roles,
-                choices_locales = locales,
-                obj = item
-            )
-        else:
-            form = AdminUpdateUserAccountForm(
-                choices_roles = roles,
-                choices_locales = locales,
-                db_item_id = item.id,
-                obj = item
-            )
-        return form
-
-
-class AddMembershipView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for adding group memberships.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    @classmethod
-    def get_view_name(cls):
-        return 'addmembership'
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return gettext('Add group membership')
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-add-member'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Add user &quot;%(user_id)s&quot; to group &quot;%(group_id)s&quot;',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def dbmodel_other(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['other'].id)
-        )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
-
-    @classmethod
-    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        # Reject item change in case given item is already enabled.
-        if kwargs['other'] in kwargs['item'].memberships:
-            return False
-        return True
-
-    @classmethod
-    def change_item(cls, **kwargs):
-        kwargs['item'].memberships.append(kwargs['other'])
-        try:
-            kwargs['item'].memberships_wanted.remove(kwargs['other'])
-        except ValueError:
-            pass
-        if kwargs['item'].is_state_disabled():
-            kwargs['item'].set_state_enabled()
-            flask.current_app.send_infomail(
-                'users.enable',
-                account = kwargs['item']
-            )
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User <strong>%(user_id)s</strong> was successfully added as a member to group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to add user <strong>%(user_id)s</strong> as a member to group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled adding user <strong>%(user_id)s</strong> as a member to group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-
-class RejectMembershipView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for rejecting group membership requests.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    @classmethod
-    def get_view_name(cls):
-        return 'rejectmembership'
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return gettext('Reject group membership')
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-rej-member'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Reject user`s &quot;%(user_id)s&quot; membership request for group &quot;%(group_id)s&quot;',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def dbmodel_other(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['other'].id)
-        )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
-
-    @classmethod
-    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        # Reject item change in case given item is already enabled.
-        if kwargs['other'] not in kwargs['item'].memberships_wanted:
-            return False
-        return True
-
-    @classmethod
-    def change_item(cls, **kwargs):
-        kwargs['item'].memberships_wanted.remove(kwargs['other'])
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User`s <strong>%(user_id)s</strong> membership request for group <strong>%(group_id)s</strong> was successfully rejected.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to reject user`s <strong>%(user_id)s</strong> membership request for group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled rejecting user`s <strong>%(user_id)s</strong> membership request for group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-
-class RemoveMembershipView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for removing group memberships.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    @classmethod
-    def get_view_name(cls):
-        return 'removemembership'
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return gettext('Remove group membership')
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-rem-member'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Remove user &quot;%(user_id)s&quot; from group &quot;%(group_id)s&quot;',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def dbmodel_other(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['other'].id)
-        )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
-
-    @classmethod
-    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        # Reject item change in case given item is already enabled.
-        if kwargs['other'] not in kwargs['item'].memberships:
-            return False
-        return True
-
-    @classmethod
-    def change_item(cls, **kwargs):
-        try:
-            kwargs['item'].memberships.remove(kwargs['other'])
-        except ValueError:
-            pass
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User <strong>%(user_id)s</strong> was successfully removed as a member from group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to remove user <strong>%(user_id)s</strong> as a member from group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled removing user <strong>%(user_id)s</strong> as a member from group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-
-class AddManagementView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for adding group managements.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    @classmethod
-    def get_view_name(cls):
-        return 'addmanagement'
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return gettext('Add group management')
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-add-manager'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Add user &quot;%(user_id)s&quot; to group &quot;%(group_id)s&quot; as manager',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def dbmodel_other(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['other'].id)
-        )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
-
-    @classmethod
-    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        # Reject item change in case given item is already manager.
-        if kwargs['other'] in kwargs['item'].managements:
-            return False
-        return True
-
-    @classmethod
-    def change_item(cls, **kwargs):
-        kwargs['item'].managements.append(kwargs['other'])
-        if kwargs['item'].is_state_disabled():
-            kwargs['item'].set_state_enabled()
-            flask.current_app.send_infomail(
-                'users.enable',
-                account = kwargs['item']
-            )
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User <strong>%(user_id)s</strong> was successfully added as a manager to group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to add user <strong>%(user_id)s</strong> as a manager to group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled adding user <strong>%(user_id)s</strong> as a manager to group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-
-class RemoveManagementView(HTMLMixin, SQLAlchemyMixin, ItemObjectRelationView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for removing group managements.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    @classmethod
-    def get_view_name(cls):
-        return 'removemanagement'
-
-    @classmethod
-    def get_view_title(cls, **kwargs):
-        return gettext('Remove group management')
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-rem-manager'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Remove user &quot;%(user_id)s&quot; from group &quot;%(group_id)s&quot; as manager',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def dbmodel_other(self):
-        return self.get_model(hawat.const.MODEL_GROUP)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @classmethod
-    def authorize_item_action(cls, **kwargs):
-        permission_m = flask_principal.Permission(
-            vial.acl.ManagementNeed(kwargs['other'].id)
-        )
-        return vial.acl.PERMISSION_POWER.can() or permission_m.can()
-
-    @classmethod
-    def validate_item_change(cls, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        # Reject item change in case given item is not already manager.
-        if kwargs['other'] not in kwargs['item'].managements:
-            return False
-        return True
-
-    @classmethod
-    def change_item(cls, **kwargs):
-        try:
-            kwargs['item'].managements.remove(kwargs['other'])
-        except ValueError:
-            pass
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User <strong>%(user_id)s</strong> was successfully removed as a manager from group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to remove user <strong>%(user_id)s</strong> as a manager from group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled removing user <strong>%(user_id)s</strong> as a manager from group <strong>%(group_id)s</strong>.',
-            user_id  = flask.escape(str(kwargs['item'])),
-            group_id = flask.escape(str(kwargs['other']))
-        )
-
-
-class EnableView(HTMLMixin, SQLAlchemyMixin, ItemEnableView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for enabling existing user accounts.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    authorization = [vial.acl.PERMISSION_POWER]
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-enable-user'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Enable user account &quot;%(item)s&quot;',
-            item = flask.escape(kwargs['item'].login)
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User account <strong>%(item_id)s</strong> was successfully enabled.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to enable user account <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled enabling user account <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @classmethod
-    def inform_user(cls, account):
-        """Send infomail about user account activation."""
-        mail_locale = account.locale
-        if not mail_locale:
-            mail_locale = flask.current_app.config['BABEL_DEFAULT_LOCALE']
-
-        with force_locale(mail_locale):
-            msg = flask_mail.Message(
-                gettext(
-                    "[%(app_name)s] Account activation - %(item_id)s",
-                    app_name = flask.current_app.config['APPLICATION_NAME'],
-                    item_id = account.login
-                ),
-                recipients = [account.email],
-                bcc = flask.current_app.config['EMAIL_ADMINS']
-            )
-            msg.body = flask.render_template(
-                'users/email_activation.txt',
-                account = account
-            )
-            flask.current_app.mailer.send(msg)
-
-    def do_after_action(self, item):
-        self.inform_user(item)
-
-
-class DisableView(HTMLMixin, SQLAlchemyMixin, ItemDisableView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for disabling user accounts.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    authorization = [vial.acl.PERMISSION_POWER]
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-disable-user'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Disable user account &quot;%(item)s&quot;',
-            item = flask.escape(kwargs['item'].login)
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User account <strong>%(item_id)s</strong> was successfully disabled.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to disable user account <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled disabling user account <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-
-class DeleteView(HTMLMixin, SQLAlchemyMixin, ItemDeleteView):  # pylint: disable=locally-disabled,too-many-ancestors
-    """
-    View for deleting existing user accounts.
-    """
-    methods = ['GET','POST']
-
-    authentication = True
-
-    authorization = [vial.acl.PERMISSION_ADMIN]
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'action-delete-user'
-
-    @classmethod
-    def get_menu_legend(cls, **kwargs):
-        return lazy_gettext(
-            'Delete user account &quot;%(item)s&quot;',
-            item = flask.escape(kwargs['item'].login)
-        )
-
-    @property
-    def dbmodel(self):
-        return self.get_model(hawat.const.MODEL_USER)
-
-    @property
-    def dbchlogmodel(self):
-        return self.get_model(hawat.const.MODEL_ITEM_CHANGELOG)
-
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User account <strong>%(item_id)s</strong> was successfully and permanently deleted.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext(
-            'Unable to delete user account <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext(
-            'Canceled deleting user account <strong>%(item_id)s</strong>.',
-            item_id = flask.escape(str(kwargs['item']))
-        )
-
-
-#-------------------------------------------------------------------------------
-
-
-class UsersBlueprint(VialBlueprint):
-    """Pluggable module - user account management (*users*)."""
-
-    @classmethod
-    def get_module_title(cls):
-        return lazy_gettext('User account management')
-
-    def register_app(self, app):
-        app.menu_main.add_entry(
-            'view',
-            'admin.{}'.format(BLUEPRINT_NAME),
-            position = 40,
-            group = lazy_gettext('Object management'),
-            view = ListView
-        )
-        app.menu_auth.add_entry(
-            'view',
-            'my_account',
-            position = 10,
-            view = MeView,
-            params = lambda: {'item': flask_login.current_user}
-        )
-
-        app.set_infomailer('users.enable', EnableView.inform_user)
-
-
-#-------------------------------------------------------------------------------
-
-
-def get_blueprint():
-    """
-    Mandatory interface for :py:mod:`vial.Vial` and factory function. This function
-    must return a valid instance of :py:class:`vial.app.VialBlueprint` or
-    :py:class:`flask.Blueprint`.
-    """
-
-    hbp = UsersBlueprint(
-        BLUEPRINT_NAME,
-        __name__,
-        template_folder = 'templates',
-        url_prefix = '/{}'.format(BLUEPRINT_NAME)
-    )
-
-    hbp.register_view_class(ListView,             '/list')
-    hbp.register_view_class(CreateView,           '/create')
-    hbp.register_view_class(ShowView,             '/<int:item_id>/show')
-    hbp.register_view_class(MeView,               '/me')
-    hbp.register_view_class(UpdateView,           '/<int:item_id>/update')
-    hbp.register_view_class(AddMembershipView,    '/<int:item_id>/add_membership/<int:other_id>')
-    hbp.register_view_class(RemoveMembershipView, '/<int:item_id>/remove_membership/<int:other_id>')
-    hbp.register_view_class(RejectMembershipView, '/<int:item_id>/reject_membership/<int:other_id>')
-    hbp.register_view_class(AddManagementView,    '/<int:item_id>/add_management/<int:other_id>')
-    hbp.register_view_class(RemoveManagementView, '/<int:item_id>/remove_management/<int:other_id>')
-    hbp.register_view_class(EnableView,           '/<int:item_id>/enable')
-    hbp.register_view_class(DisableView,          '/<int:item_id>/disable')
-    hbp.register_view_class(DeleteView,           '/<int:item_id>/delete')
-
-    return hbp
diff --git a/lib/vial/blueprints/users/forms.py b/lib/vial/blueprints/users/forms.py
deleted file mode 100644
index 28d88bb8f..000000000
--- a/lib/vial/blueprints/users/forms.py
+++ /dev/null
@@ -1,284 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-This module contains custom user account management forms for Hawat.
-"""
-
-
-import pytz
-import wtforms
-from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
-from flask_babel import lazy_gettext
-
-import vial.db
-import hawat.const
-import vial.forms
-from vial.forms import check_login, check_unique_login, get_available_groups
-
-
-class BaseUserAccountForm(vial.forms.BaseItemForm):
-    """
-    Class representing base user account form.
-    """
-    fullname = wtforms.StringField(
-        lazy_gettext('Full name:'),
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.Length(min = 3, max = 100)
-        ]
-    )
-    email = wtforms.StringField(
-        lazy_gettext('Email:'),
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.Length(min = 3, max = 250),
-            wtforms.validators.Email()
-        ]
-    )
-    locale = vial.forms.SelectFieldWithNone(
-        lazy_gettext('Prefered locale:'),
-        validators = [
-            wtforms.validators.Optional()
-        ],
-        choices = [('', lazy_gettext('<< no preference >>'))],
-        filters = [lambda x: x or None],
-        default = ''
-    )
-    timezone = vial.forms.SelectFieldWithNone(
-        lazy_gettext('Prefered timezone:'),
-        validators = [
-            wtforms.validators.Optional(),
-        ],
-        choices = [('', lazy_gettext('<< no preference >>'))] + list(zip(pytz.common_timezones, pytz.common_timezones)),
-        filters = [lambda x: x or None],
-        default = ''
-    )
-    submit = wtforms.SubmitField(
-        lazy_gettext('Submit')
-    )
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        #
-        # Handle additional custom keywords.
-        #
-
-        # The list of choices for 'locale' attribute comes from outside of the
-        # form to provide as loose tie as possible to the outer application.
-        # Another approach would be to load available choices here with:
-        #
-        #   locales = list(flask.current_app.config['SUPPORTED_LOCALES'].items())
-        #
-        # That would mean direct dependency on flask.Flask application.
-        self.locale.choices[1:] = kwargs['choices_locales']
-
-
-class AdminUserAccountForm(BaseUserAccountForm):
-    """
-    Class representing base user account form for admins.
-    """
-    enabled = wtforms.RadioField(
-        lazy_gettext('State:'),
-        validators = [
-            wtforms.validators.InputRequired(),
-        ],
-        choices = [
-            (True,  lazy_gettext('Enabled')),
-            (False, lazy_gettext('Disabled'))
-        ],
-        filters = [vial.forms.str_to_bool],
-        coerce = vial.forms.str_to_bool
-    )
-    roles = wtforms.SelectMultipleField(
-        lazy_gettext('Roles:'),
-        validators = [
-            wtforms.validators.Optional()
-        ]
-    )
-    memberships = QuerySelectMultipleField(
-        lazy_gettext('Group memberships:'),
-        query_factory = get_available_groups
-    )
-    managements = QuerySelectMultipleField(
-        lazy_gettext('Group managements:'),
-        query_factory = get_available_groups
-    )
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        #
-        # Handle additional custom keywords.
-        #
-
-        # The list of choices for 'roles' attribute comes from outside of the
-        # form to provide as loose tie as possible to the outer application.
-        # Another approach would be to load available choices here with:
-        #
-        #   roles = flask.current_app.config['ROLES']
-        #
-        # That would mean direct dependency on flask.Flask application.
-        self.roles.choices = kwargs['choices_roles']
-
-
-class CreateUserAccountForm(AdminUserAccountForm):
-    """
-    Class representing user account create form.
-    """
-    login = wtforms.StringField(
-        lazy_gettext('Login:'),
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.Length(min = 3, max = 50),
-            check_login,
-            check_unique_login
-        ]
-    )
-
-
-class UpdateUserAccountForm(BaseUserAccountForm):
-    """
-    Class representing user account update form for regular users.
-    """
-
-
-class AdminUpdateUserAccountForm(AdminUserAccountForm):
-    """
-    Class representing user account update form for administrators.
-    """
-    login = wtforms.StringField(
-        lazy_gettext('Login:'),
-        validators = [
-            wtforms.validators.DataRequired(),
-            wtforms.validators.Length(min = 3, max = 50),
-            vial.forms.check_login,
-            check_unique_login
-        ]
-    )
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        #
-        # Handle additional custom keywords.
-        #
-
-        # Store the ID of original item in database to enable the ID uniqueness
-        # check with check_id_uniqueness() validator.
-        self.db_item_id = kwargs['db_item_id']
-
-
-class UserSearchForm(vial.forms.BaseSearchForm):
-    """
-    Class representing simple user search form.
-    """
-    search = wtforms.StringField(
-        lazy_gettext('Login, name, email:'),
-        validators = [
-            wtforms.validators.Optional(),
-            wtforms.validators.Length(min = 3, max = 100)
-        ],
-        description = lazy_gettext('User`s login, full name or email address. Search is performed even in the middle of the strings, so for example you may lookup by domain.')
-    )
-    dt_from = vial.forms.SmartDateTimeField(
-        lazy_gettext('Creation time from:'),
-        validators = [
-            wtforms.validators.Optional()
-        ],
-        description = lazy_gettext('Lower time boundary for item creation time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences.')
-    )
-    dt_to = vial.forms.SmartDateTimeField(
-        lazy_gettext('Creation time to:'),
-        validators = [
-            wtforms.validators.Optional()
-        ],
-        description = lazy_gettext('Upper time boundary for item creation time. Timestamp is expected to be in the format <code>YYYY-MM-DD hh:mm:ss</code> and in the timezone according to the user`s preferences.')
-    )
-
-    state = wtforms.SelectField(
-        lazy_gettext('State:'),
-        validators = [
-            wtforms.validators.Optional(),
-        ],
-        choices = [
-            ('', lazy_gettext('Nothing selected')),
-            ('enabled',  lazy_gettext('Enabled')),
-            ('disabled', lazy_gettext('Disabled'))
-        ],
-        default = '',
-        description = lazy_gettext('Search for users with particular account state.')
-    )
-    role = wtforms.SelectField(
-        lazy_gettext('Role:'),
-        validators = [
-            wtforms.validators.Optional()
-        ],
-        default = '',
-        description = lazy_gettext('Search for users with particular role, or without any assigned roles.')
-    )
-    membership = QuerySelectField(
-        lazy_gettext('Group membership:'),
-        query_factory = get_available_groups,
-        allow_blank = True,
-        description = lazy_gettext('Search for users with membership with particular group.')
-    )
-    management = QuerySelectField(
-        lazy_gettext('Group management:'),
-        query_factory = get_available_groups,
-        allow_blank = True,
-        description = lazy_gettext('Search for users with management rights to particular group.')
-    )
-
-    sortby = wtforms.SelectField(
-        lazy_gettext('Sort result by:'),
-        validators = [
-            wtforms.validators.Optional()
-        ],
-        choices = [
-            ('createtime.desc', lazy_gettext('by creation time descending')),
-            ('createtime.asc',  lazy_gettext('by creation time ascending')),
-            ('login.desc', lazy_gettext('by login descending')),
-            ('login.asc',  lazy_gettext('by login ascending')),
-            ('fullname.desc', lazy_gettext('by name descending')),
-            ('fullname.asc',  lazy_gettext('by name ascending')),
-            ('email.desc', lazy_gettext('by email descending')),
-            ('email.asc',  lazy_gettext('by email ascending')),
-            ('logintime.desc', lazy_gettext('by login time descending')),
-            ('logintime.asc',  lazy_gettext('by login time ascending')),
-        ],
-        default = 'fullname.asc'
-    )
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        #
-        # Handle additional custom keywords.
-        #
-
-        # The list of choices for 'roles' attribute comes from outside of the
-        # form to provide as loose tie as possible to the outer application.
-        # Another approach would be to load available choices here with:
-        #
-        #   roles = flask.current_app.config['ROLES']
-        #
-        # That would mean direct dependency on flask.Flask application.
-        self.role.choices = [
-            ('', lazy_gettext('Nothing selected')),
-            (hawat.const.NO_ROLE, lazy_gettext('Without any roles'))
-        ] + kwargs['choices_roles']
-
-    @staticmethod
-    def is_multivalue(field_name):
-        """
-        Check, if given form field is a multivalue field.
-
-        :param str field_name: Name of the form field.
-        :return: ``True``, if the field can contain multiple values, ``False`` otherwise.
-        :rtype: bool
-        """
-        return False
diff --git a/lib/vial/blueprints/users/templates/users/addmembership.html b/lib/vial/blueprints/users/templates/users/addmembership.html
deleted file mode 100644
index 75fc075cf..000000000
--- a/lib/vial/blueprints/users/templates/users/addmembership.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{%- extends "_layout_confirmation.html" %}
-
-{%- block modalcontent %}
-                                <p>
-                                    {{ _('Are you really sure you want to add user') }}
-                                </p>
-                                <p class="lead text-center">
-                                    {{ item_name }}
-                                </p>
-                                <p>
-                                    {{ _('to abuse group') }}
-                                </p>
-                                <p class="lead text-center">
-                                    {{ other_name }}
-                                </p>
-{% endblock modalcontent %}
diff --git a/lib/vial/blueprints/users/templates/users/creatupdate.html b/lib/vial/blueprints/users/templates/users/creatupdate.html
deleted file mode 100644
index 2f97a58bb..000000000
--- a/lib/vial/blueprints/users/templates/users/creatupdate.html
+++ /dev/null
@@ -1,38 +0,0 @@
-{%- extends "_layout_creatupdate.html" %}
-
-{%- block itemform_fields %}
-
-    {%- if item_action == 'create' or current_user.has_role('admin') %}
-
-                                {{ macros_form.render_form_item_default(form.login) }}
-
-    {%- elif item_action == 'update' %}
-
-                                {{ macros_form.render_form_item_static(_('Login:'), item.login) }}
-
-    {%- endif %}
-
-                                {{ macros_form.render_form_item_default(form.fullname) }}
-
-                                {{ macros_form.render_form_item_default(form.email) }}
-
-    {%- if item_action == 'create' or current_user.has_role('admin') %}
-
-                                <hr>
-
-                                {{ macros_form.render_form_item_radiobutton(form.enabled) }}
-
-                                {{ macros_form.render_form_item_select(form.roles) }}
-
-                                {{ macros_form.render_form_item_select(form.memberships) }}
-
-                                {{ macros_form.render_form_item_select(form.managements) }}
-    {%- endif %}
-
-                                <hr>
-
-                                {{ macros_form.render_form_item_select(form.locale) }}
-
-                                {{ macros_form.render_form_item_select(form.timezone) }}
-
-{%- endblock itemform_fields %}
diff --git a/lib/vial/blueprints/users/templates/users/email_activation.txt b/lib/vial/blueprints/users/templates/users/email_activation.txt
deleted file mode 100644
index df918e00e..000000000
--- a/lib/vial/blueprints/users/templates/users/email_activation.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-{{ _('Dear user,') | wordwrap }}
-
-{{ _('this email is a confirmation, that your account "%(item_id)s" in %(app_name)s was just activated. You may now login and start using the system:', item_id = account.login, app_name = vial_appname) | wordwrap }}
-
-	{{ url_for('home.index', _external = True ) }}
-
-{{ _('Have a nice day') | wordwrap }}
-
--- {{ vial_appname }}
diff --git a/lib/vial/blueprints/users/templates/users/list.html b/lib/vial/blueprints/users/templates/users/list.html
deleted file mode 100644
index 732c1f334..000000000
--- a/lib/vial/blueprints/users/templates/users/list.html
+++ /dev/null
@@ -1,78 +0,0 @@
-{%- extends "_layout_list.html" %}
-
-{%- block searchformfields %}
-
-                            <div class="row">
-                                <div class="col-sm-4">
-                                    {{ macros_form.render_form_item_default(g.search_form.search) }}
-                                </div>
-                                <div class="col-sm-4">
-                                    {{ macros_form.render_form_item_datetime(g.search_form.dt_from, 'datetimepicker-hm-from') }}
-                                </div>
-                                <div class="col-sm-4">
-                                    {{ macros_form.render_form_item_datetime(g.search_form.dt_to, 'datetimepicker-hm-to') }}
-                                </div>
-                            </div>
-                            <div class="row">
-                                <div class="col-sm-3">
-                                    {{ macros_form.render_form_item_select(g.search_form.state) }}
-                                </div>
-                                <div class="col-sm-3">
-                                    {{ macros_form.render_form_item_select(g.search_form.role) }}
-                                </div>
-                                <div class="col-sm-3">
-                                    {{ macros_form.render_form_item_select(g.search_form.membership) }}
-                                </div>
-                                <div class="col-sm-3">
-                                    {{ macros_form.render_form_item_select(g.search_form.management) }}
-                                </div>
-                            </div>
-
-{%- endblock searchformfields %}
-
-{%- block contentinner %}
-
-                    <table class="table table-bordered table-hover">
-                        <thead>
-                            <tr>
-                                <th>
-                                    {{ _('Login') }}
-                                </th>
-                                <th>
-                                    {{ _('Name') }}
-                                </th>
-                                <th>
-                                    {{ _('Roles') }}
-                                </th>
-                                <th>
-                                    {{ _('State') }}
-                                </th>
-                                <th data-toggle="tooltip" title="{{ _('Contextual item actions') }}">
-                                    {{ get_icon('actions') }} {{ _('Actions') }}
-                                </th>
-                            </tr>
-                        </thead>
-                        <tbody>
-                            {%- for item in items %}
-                            <tr{% if item.has_no_role() %} class="warning"{% elif item.has_role('admin') %} class="info"{% endif %}>
-                                <td>
-                                    {{ item.login | default(_('<< unknown >>'), True) }}
-                                </td>
-                                <td>
-                                    {{ item.fullname | default(_('<< unknown >>'), True) }}
-                                </td>
-                                <td>
-                                    {{ macros_site.render_labels_role_list(config['ROLES'], item, True) }}
-                                </td>
-                                <td>
-                                    {{ macros_site.render_label_item_state(item.enabled, True) }}
-                                </td>
-                                <td class="column-actions">
-                                    {{ macros_page.render_menu_context_actions(item) }}
-                                </td>
-                            </tr>
-                            {%- endfor %}
-                        </tbody>
-                    </table>
-
-{%- endblock contentinner %}
diff --git a/lib/vial/blueprints/users/templates/users/rejectmembership.html b/lib/vial/blueprints/users/templates/users/rejectmembership.html
deleted file mode 100644
index ec04d6b5a..000000000
--- a/lib/vial/blueprints/users/templates/users/rejectmembership.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{%- extends "_layout_confirmation.html" %}
-
-{%- block modalcontent %}
-                                <p>
-                                    {{ _('Are you really sure you want to reject membership request of user') }}
-                                </p>
-                                <p class="lead text-center">
-                                    {{ item_name }}
-                                </p>
-                                <p>
-                                    {{ _('for abuse group') }}
-                                </p>
-                                <p class="lead text-center">
-                                    {{ other_name }}
-                                </p>
-{% endblock modalcontent %}
diff --git a/lib/vial/blueprints/users/templates/users/removemembership.html b/lib/vial/blueprints/users/templates/users/removemembership.html
deleted file mode 100644
index 1fa014985..000000000
--- a/lib/vial/blueprints/users/templates/users/removemembership.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{%- extends "_layout_confirmation.html" %}
-
-{%- block modalcontent %}
-                                <p>
-                                    {{ _('Are you really sure you want to remove user') }}
-                                </p>
-                                <p class="lead text-center">
-                                    {{ item_name }}
-                                </p>
-                                <p>
-                                    {{ _('from abuse group') }}
-                                </p>
-                                <p class="lead text-center">
-                                    {{ other_name }}
-                                </p>
-{% endblock modalcontent %}
diff --git a/lib/vial/blueprints/users/templates/users/show.html b/lib/vial/blueprints/users/templates/users/show.html
deleted file mode 100644
index dde19de1d..000000000
--- a/lib/vial/blueprints/users/templates/users/show.html
+++ /dev/null
@@ -1,281 +0,0 @@
-{% extends "_layout.html" %}
-
-{% block content %}
-
-            <div class="row">
-                <div class="col-lg-12">
-                    {{ macros_page.render_breadcrumbs(item) }}
-
-                    <h2>{{ vial_current_view.get_view_title() }}</h2>
-                    <hr>
-                    <h3>{{ item.fullname }} ({{ item.login }})</h3>
-                    <div class="pull-right">
-                        {{ macros_page.render_menu_actions(item) }}
-
-                    </div>
-                    <p>
-                        <small>
-                            <strong>{{ _('Account created') }}:</strong> {{ babel_format_datetime(item.createtime) }} ({{ _('%(delta)s ago', delta = babel_format_timedelta(current_datetime_utc - item.createtime)) }})
-                            &nbsp;|&nbsp;
-                            <strong>{{ _('Last login') }}:</strong> {% if  item.logintime %}{{ babel_format_datetime(item.logintime) }} ({{ _('%(delta)s ago', delta = babel_format_timedelta(current_datetime_utc - item.logintime)) }}){% else %}{{ _('never') }}{% endif %}
-                        </small>
-                    </p>
-                    <br>
-
-                    <!-- Nav tabs -->
-                    <ul class="nav nav-tabs" role="tablist">
-                        <li role="presentation" class="active">
-                            <a href="#tab-general" aria-controls="tab-general" role="tab" data-toggle="tab">
-                                <strong>{{ get_icon('alert-info') }} {{ _('General information') }}</strong>
-                            </a>
-                        </li>
-                        {%- if can_access_endpoint('users.update', item) %}
-                        <li role="presentation">
-                            <a href="#tab-actionlog" aria-controls="tab-actionlog" role="tab" data-toggle="tab">
-                                <strong>{{ get_icon('module-changelogs') }} {{ _('Actionlogs') }}</strong> <span class="badge">{{ user_changelog | length }}</span>
-                            </a>
-                        </li>
-                        {%- endif %}
-                        {%- if can_access_endpoint('users.update', item) %}
-                        <li role="presentation">
-                            <a href="#tab-changelog" aria-controls="tab-changelog" role="tab" data-toggle="tab">
-                                <strong>{{ get_icon('module-changelogs') }} {{ _('Changelogs') }}</strong> <span class="badge">{{ item_changelog | length }}</span>
-                            </a>
-                        </li>
-                        {%- endif %}
-                    </ul>
-
-                    <!-- Tab panes -->
-                    <div class="tab-content">
-
-                        <div role="tabpanel" class="tab-pane fade in active" id="tab-general">
-                            <br>
-                            <div class="row">
-
-                                <div class="col-md-4">
-                                    <h4>{{ _('Metadata') }}</h4>
-                                    <table class="table table-striped">
-                                        <tbody>
-                                            <tr>
-                                                <th>
-                                                    {{ _('Login') }}:
-                                                </th>
-                                                <td>
-                                                    {{ item.login | default(_('<< unknown >>'), True) }}
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th>
-                                                    {{ _('Full name') }}:
-                                                </th>
-                                                <td>
-                                                    {{ item.fullname | default(_('<< unknown >>'), True) }}
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th>
-                                                    {{ _('Email') }}:
-                                                </th>
-                                                <td>
-                                                    {%- if item.email %}
-                                                    <a href="mailto:{{ item.email }}">{{ item.email }}</a>
-                                                    {%- else %}
-                                                    {{ _('<< unknown >>') }}
-                                                    {%- endif %}
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th>
-                                                    {{ _('State') }}:
-                                                </th>
-                                                <td>
-                                                    {{ macros_site.render_label_item_state(item.enabled, True) }}
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th>
-                                                    {{ _('Roles') }}:
-                                                </th>
-                                                <td>
-                                                    {{ macros_site.render_labels_role_list(config['ROLES'], item, True) }}
-                                                </td>
-                                            </tr>
-                                            {%- if can_access_endpoint('auth_api.key-generate', item) or item.apikey %}
-                                            <tr>
-                                                <th>
-                                                    {{ _('API access key') }}:
-                                                </th>
-                                                <td>
-                                                    {%- if item.apikey %}
-                                                    <div class="input-group">
-                                                        {%- if is_it_me(item) %}
-                                                        <input type="text" value="{{ item.apikey }}" readonly="readonly" class="form-control">
-                                                        {%- endif %}
-                                                        <span class="input-group-btn">
-                                                            <a role="button" class="btn btn-default" href="{{ url_for('auth_api.key-delete', item_id = item.id, next = request.url) }}" data-toggle="tooltip" title="{{ get_endpoint_class('auth_api.key-delete').get_menu_legend() }}">
-                                                                {{ get_icon(get_endpoint_class('auth_api.key-delete').get_view_icon()) }}{% if not is_it_me(item) %} {{ get_endpoint_class('auth_api.key-delete').get_menu_title()}}{% endif %}
-                                                            </a>
-                                                        </span>
-                                                    </div>
-                                                    {%- else %}
-                                                    <div class="input-group">
-                                                        <span class="input-group-btn">
-                                                            <a role="button" class="btn btn-default btn-sm" href="{{ url_for('auth_api.key-generate', item_id = item.id, next = request.url) }}" data-toggle="tooltip" title="{{ get_endpoint_class('auth_api.key-generate').get_menu_legend() }}">
-                                                                {{ get_icon(get_endpoint_class('auth_api.key-generate').get_view_icon()) }} {{ get_endpoint_class('auth_api.key-generate').get_menu_title()}}
-                                                            </a>
-                                                        </span>
-                                                    </div>
-                                                    {%- endif %}
-                                                </td>
-                                            </tr>
-                                            {%- endif %}
-                                            <tr>
-                                                <th>
-                                                    {{ _('Prefered locale') }}:
-                                                </th>
-                                                <td>
-                                                    {%- if item.locale %}
-                                                    {{ get_country_flag(item.locale|upper) }} {{ babel_translate_locale(item.locale, True) }} ({{ item.locale }})
-                                                    {%- else %}
-                                                    {{ _('<< system default >>') }}
-                                                    {%- endif %}
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th>
-                                                    {{ _('Prefered timezone') }}:
-                                                </th>
-                                                <td>
-                                                    {%- if item.timezone %}
-                                                    {{ item.timezone }}
-                                                    {%- else %}
-                                                    {{ _('<< system default >>') }}
-                                                    {%- endif %}
-                                                </td>
-                                            </tr>
-                                        </tbody>
-                                    </table>
-                                </div>
-
-                                <div class="col-md-4">
-                                    {%- if item.memberships_wanted %}
-                                    <h4>
-                                        {{ _('Group membership requests') }} <span class="badge">{{ item.memberships_wanted | length }}</span>
-                                    </h4>
-                                    <table class="table table-striped">
-                                        <tbody>
-                                            {%- for subitem in item.memberships_wanted %}
-                                            <tr>
-                                                <td>
-                                                    {{ subitem.name }}
-                                                </td>
-                                                <td>
-                                                    {{ macros_page.render_menu_context_actions(subitem, context_action_menu_groups, kwargs = {'other': item}) }}
-                                                </td>
-                                            </tr>
-                                            {%- endfor %}
-                                        </tbody>
-                                    </table>
-                                    {%- endif %}
-                                    <h4>
-                                        {{ _('Group memberships') }} <span class="badge">{{ item.memberships | length }}</span>
-                                    </h4>
-                                    {%- if item.memberships %}
-                                    <table class="table table-striped">
-                                        <tbody>
-                                            {%- for subitem in item.memberships %}
-                                            <tr>
-                                                <td>
-                                                    {{ subitem.name }}
-                                                </td>
-                                                <td>
-                                                    {{ macros_page.render_menu_context_actions(subitem, context_action_menu_groups, kwargs = {'other': item}) }}
-                                                </td>
-                                            </tr>
-                                            {%- endfor %}
-                                        </tbody>
-                                    </table>
-                                    {%- else %}
-                                    {%- call macros_site.render_alert('info', False) %}
-                                    {{ _('This user is not member of any group at the moment.') }}
-                                    {%- endcall %}
-                                    {%- endif %}
-                                </div>
-
-                                <div class="col-md-4">
-                                    <h4>{{ _('Group managements') }} <span class="badge">{{ item.managements | length }}</span></h4>
-                                    {%- if item.managements %}
-                                    <table class="table table-striped">
-                                        <tbody>
-                                            {%- for subitem in item.managements %}
-                                            <tr>
-                                                <td>
-                                                    {{ subitem.name }}
-                                                </td>
-                                                <td>
-                                                    {{ macros_page.render_menu_context_actions(subitem, context_action_menu_groups, kwargs = {'other': item}) }}
-                                                </td>
-                                            </tr>
-                                            {%- endfor %}
-                                        </tbody>
-                                    </table>
-                                    {%- else %}
-                                    {%- call macros_site.render_alert('info', False) %}
-                                    {{ _('This user is not manager of any group at the moment.') }}
-                                    {%- endcall %}
-                                    {%- endif %}
-                                </div>
-
-                            </div>
-                        </div>
-
-                        {%- if can_access_endpoint('users.update', item) %}
-                        <div role="tabpanel" class="tab-pane fade" id="tab-actionlog">
-                            <br>
-                            {%- if user_changelog %}
-                            {{ macros_page.render_changelog_records(user_changelog, context_action_menu_changelogs, idprefix = 'actionlog', hide_author = True) }}
-                            <p>
-                                <small>
-                                    {{ _('Displaying only latest %(count)s actionlogs', count = 100) }}
-                                </small>
-                            </p>
-                            {%- else %}
-                            {%- call macros_site.render_alert('info', False) %}
-                            {{ _('This user does not have any actionlog records at the moment.') }}
-                            {%- endcall %}
-                            {%- endif %}
-                        </div>
-                        {%- endif %}
-
-                        {%- if can_access_endpoint('users.update', item) %}
-                        <div role="tabpanel" class="tab-pane fade" id="tab-changelog">
-                            <br>
-                            {%- if item_changelog %}
-                            {{ macros_page.render_changelog_records(item_changelog, context_action_menu_changelogs, hide_item = True) }}
-                            <p>
-                                <small>
-                                    {{ _('Displaying only latest %(count)s changelogs', count = 100) }}
-                                </small>
-                            </p>
-                            {%- else %}
-                            {%- call macros_site.render_alert('info', False) %}
-                            {{ _('This object does not have any changelog records at the moment.') }}
-                            {%- endcall %}
-                            {%- endif %}
-                        </div>
-                        {%- endif %}
-
-                    </div>
-
-                </div><!-- /.col-lg-12 -->
-            </div><!-- /.row -->
-
-    {%- if permission_can('developer') %}
-
-            <hr>
-
-            {{ macros_site.render_raw_item_view(item) }}
-
-    {%- endif %}
-
-{% endblock content %}
diff --git a/lib/vial/blueprints/users/test/__init__.py b/lib/vial/blueprints/users/test/__init__.py
deleted file mode 100644
index 7dbd927e0..000000000
--- a/lib/vial/blueprints/users/test/__init__.py
+++ /dev/null
@@ -1,606 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-Unit tests for :py:mod:`vial.blueprints.users`.
-"""
-
-
-import sys
-import unittest
-
-import hawat.const
-import vial.db
-import vial.test
-import vial.test.fixtures
-import vial.test.runner
-from vial.test import VialTestCase, ItemCreateVialTestCase
-from vial.test.runner import TestRunnerMixin
-from vial.blueprints.users.test.utils import UsersTestCaseMixin
-
-_IS_NOSE = sys.argv[0].endswith('nosetests')
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class UsersListTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
-    """Class for testing ``users.list`` endpoint."""
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """
-        Test access as user ``user``.
-
-        Only power user is able to list all available user accounts.
-        """
-        self._attempt_fail_list()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_02_as_developer(self):
-        """
-        Test access as user ``developer``.
-
-        Only power user is able to list all available user accounts.
-        """
-        self._attempt_fail_list()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_03_as_maintainer(self):
-        """
-        Test access as user ``maintainer``.
-
-        Only power user is able to list all available user accounts.
-        """
-        self._attempt_succeed_list()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_04_as_admin(self):
-        """
-        Test access as user ``admin``.
-
-        Only power user is able to list all available user accounts.
-        """
-        self._attempt_succeed_list()
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class UsersShowOwnTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
-    """
-    Class for testing ``users.show`` endpoint: access to user`s own accounts.
-
-    Each user must be able to access his own account.
-    """
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """Test access as user 'user'."""
-        self._attempt_succeed_show(hawat.const.ROLE_USER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_02_as_developer(self):
-        """Test access as user 'developer'."""
-        self._attempt_succeed_show(hawat.const.ROLE_DEVELOPER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_03_as_maintainer(self):
-        """Test access as user 'maintainer'."""
-        self._attempt_succeed_show(hawat.const.ROLE_MAINTAINER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_04_as_admin(self):
-        """Test access as user 'admin'."""
-        self._attempt_succeed_show(hawat.const.ROLE_ADMIN)
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class UsersShowOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
-    """Class for testing ``users.show`` endpoint: access to other user`s accounts."""
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user_developer(self):
-        """
-        Test access to 'developer' account as user 'user'.
-
-        Regular user may view only his own account.
-        """
-        self._attempt_fail_show(hawat.const.ROLE_DEVELOPER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_02_as_user_maintainer(self):
-        """
-        Test access to 'maintainer' account as user 'user'.
-
-        Regular user may view only his own account.
-        """
-        self._attempt_fail_show(hawat.const.ROLE_MAINTAINER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_03_as_user_admin(self):
-        """
-        Test access to 'admin' account as user 'user'.
-
-        Regular user may view only his own account.
-        """
-        self._attempt_fail_show(hawat.const.ROLE_ADMIN)
-
-    #--------------------------------------------------------------------------
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_04_as_developer_user(self):
-        """
-        Test access to 'user' account as user 'developer'.
-
-        Developer should be able to access because he is a manager of group of
-        which all other users are members.
-        """
-        self._attempt_succeed_show(hawat.const.ROLE_USER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_05_as_developer_maintainer(self):
-        """
-        Test access to 'maintainer' account as user 'developer'.
-
-        Developer should be able to access because he is a manager of group of
-        which all other users are members.
-        """
-        self._attempt_succeed_show(hawat.const.ROLE_MAINTAINER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_06_as_developer_admin(self):
-        """
-        Test access to 'admin' account as user 'developer'.
-
-        Developer should be able to access because he is a manager of group of
-        which all other users are members.
-        """
-        self._attempt_succeed_show(hawat.const.ROLE_ADMIN)
-
-    #--------------------------------------------------------------------------
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_07_as_maintainer_user(self):
-        """
-        Test access to 'user' account as user 'maintainer'.
-
-        Maintainer should be allowed access, because he is a power user like admin.
-        """
-        self._attempt_succeed_show(hawat.const.ROLE_USER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_08_as_maintainer_developer(self):
-        """
-        Test access to 'developer' account as user 'maintainer'.
-
-        Maintainer should be allowed access, because he is a power user like admin.
-        """
-        self._attempt_succeed_show(hawat.const.ROLE_DEVELOPER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_09_as_maintainer_admin(self):
-        """
-        Test access to 'maintainer' account as user 'maintainer'.
-
-        Maintainer should be allowed access, because he is a power user like admin.
-        """
-        self._attempt_succeed_show(hawat.const.ROLE_MAINTAINER)
-
-    #--------------------------------------------------------------------------
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_10_as_admin_user(self):
-        """Test access to 'user' account as user 'admin'."""
-        self._attempt_succeed_show(hawat.const.ROLE_USER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_11_as_admin_developer(self):
-        """Test access to 'developer' account as user 'admin'."""
-        self._attempt_succeed_show(hawat.const.ROLE_DEVELOPER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_12_as_admin_maintainer(self):
-        """Test access to 'maintainer' account as user 'admin'."""
-        self._attempt_succeed_show(hawat.const.ROLE_MAINTAINER)
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class UsersCreateTestCase(UsersTestCaseMixin, TestRunnerMixin, ItemCreateVialTestCase):
-    """Class for testing ``users.create`` endpoint."""
-
-    user_data_fixture = [
-        ('login', 'test'),
-        ('fullname', 'Test User'),
-        ('email', 'test.user@domain.org'),
-        ('enabled', True)
-    ]
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """Test access as user 'user'."""
-        self._attempt_fail_create()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_02_as_developer(self):
-        """Test access as user 'developer'."""
-        self._attempt_fail_create()
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_03_as_maintainer(self):
-        """Test access as user 'maintainer'."""
-        self._attempt_succeed_create(self.user_data_fixture)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_04_as_admin(self):
-        """Test access as user 'admin'."""
-        self._attempt_succeed_create(self.user_data_fixture)
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class UsersUpdateOwnTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
-    """Class for testing ``users.update`` endpoint: access to user`s own accounts."""
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """Test access as user 'user'."""
-        self._attempt_succeed_update(hawat.const.ROLE_USER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_02_as_developer(self):
-        """Test access as user 'developer'."""
-        self._attempt_succeed_update(hawat.const.ROLE_DEVELOPER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_03_as_maintainer(self):
-        """Test access as user 'maintainer'."""
-        self._attempt_succeed_update(hawat.const.ROLE_MAINTAINER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_04_as_admin(self):
-        """Test access as user 'admin'."""
-        self._attempt_succeed_update(hawat.const.ROLE_ADMIN)
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class UsersUpdateOtherTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
-    """Class for testing ``users.update`` endpoint: access to other user`s accounts."""
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user_developer(self):
-        """Test access to 'developer' account as user 'user'."""
-        self._attempt_fail_update(hawat.const.ROLE_DEVELOPER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_02_as_user_maintainer(self):
-        """Test access to 'maintainer' account as user 'user'."""
-        self._attempt_fail_update(hawat.const.ROLE_MAINTAINER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_03_as_user_admin(self):
-        """Test access to 'admin' account as user 'user'."""
-        self._attempt_fail_update(hawat.const.ROLE_ADMIN)
-
-    #--------------------------------------------------------------------------
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_04_as_developer_user(self):
-        """Test access to 'user' account as user 'developer'."""
-        self._attempt_fail_update(hawat.const.ROLE_USER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_05_as_developer_maintainer(self):
-        """Test access to 'maintainer' account as user 'developer'."""
-        self._attempt_fail_update(hawat.const.ROLE_MAINTAINER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_06_as_developer_admin(self):
-        """Test access to 'admin' account as user 'developer'."""
-        self._attempt_fail_update(hawat.const.ROLE_ADMIN)
-
-    #--------------------------------------------------------------------------
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_07_as_maintainer_user(self):
-        """Test access to 'user' account as user 'maintainer'."""
-        self._attempt_fail_update(hawat.const.ROLE_USER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_08_as_maintainer_developer(self):
-        """Test access to 'developer' account as user 'maintainer'."""
-        self._attempt_fail_update(hawat.const.ROLE_DEVELOPER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_09_as_maintainer_admin(self):
-        """Test access to 'admin' account as user 'maintainer'."""
-        self._attempt_fail_update(hawat.const.ROLE_ADMIN)
-
-    #--------------------------------------------------------------------------
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_10_as_admin_user(self):
-        """Test access to 'user' account as user 'admin'."""
-        self._attempt_succeed_update(hawat.const.ROLE_USER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_11_as_admin_developer(self):
-        """Test access to 'developer' account as user 'admin'."""
-        self._attempt_succeed_update(hawat.const.ROLE_DEVELOPER)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_12_as_admin_maintainer(self):
-        """Test access to 'maintainer' account as user 'admin'."""
-        self._attempt_succeed_update(hawat.const.ROLE_MAINTAINER)
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class UsersEnableDisableTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
-    """Class for testing ``users.enable`` and ``users.disable`` endpoint."""
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """Test access as user 'user'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_MAINTAINER,
-                hawat.const.ROLE_ADMIN
-            ):
-            self._attempt_fail_disable(uname)
-            self._attempt_fail_enable(uname)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_02_as_developer(self):
-        """Test access as user 'developer'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_MAINTAINER,
-                hawat.const.ROLE_ADMIN
-            ):
-            self._attempt_fail_disable(uname)
-            self._attempt_fail_enable(uname)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_03_as_maintainer(self):
-        """Test access as user 'maintainer'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_ADMIN
-            ):
-            self._attempt_succeed_disable(uname)
-            self._attempt_succeed_enable(uname)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_04_as_admin(self):
-        """Test access as user 'admin'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_MAINTAINER
-            ):
-            self._attempt_succeed_disable(uname)
-            self._attempt_succeed_enable(uname)
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class UsersAddRemRejMembershipTestCase(TestRunnerMixin, VialTestCase):
-    """Class for testing ``users.add_membership``, ``users.reject_membership`` and ``users.remove_membership`` endpoint."""
-
-    def _attempt_fail(self, uname, gname):
-        with self.app.app_context():
-            uid = self.user_id(uname)
-            gid = self.group_id(gname)
-        self.assertGetURL(
-            '/users/{}/remove_membership/{}'.format(uid, gid),
-            403
-        )
-        self.assertGetURL(
-            '/users/{}/reject_membership/{}'.format(uid, gid),
-            403
-        )
-        self.assertGetURL(
-            '/users/{}/add_membership/{}'.format(uid, gid),
-            403
-        )
-
-    def _attempt_succeed(self, uname, gname, print_response = False):
-        # Additional test preparations.
-        with self.app.app_context():
-            uid = self.user_id(uname)
-            gid = self.group_id(gname)
-            self.user_enabled(uname, False)
-        self.mailbox_monitoring('on')
-
-        #
-        # First check the removal of existing membership.
-        #
-        self.assertGetURL(
-            '/users/{}/remove_membership/{}'.format(uid, gid),
-            200,
-            [
-                b'Are you really sure you want to remove user'
-            ],
-            print_response
-        )
-        self.assertPostURL(
-            '/users/{}/remove_membership/{}'.format(uid, gid),
-            {
-                'submit': 'Confirm'
-            },
-            200,
-            [
-                b'was successfully removed as a member from group'
-            ],
-            print_response
-        )
-
-        #
-        # Add user back to group.
-        #
-        self.assertGetURL(
-            '/users/{}/add_membership/{}'.format(uid, gid),
-            200,
-            [
-                b'Are you really sure you want to add user'
-            ],
-            print_response
-        )
-        self.assertPostURL(
-            '/users/{}/add_membership/{}'.format(uid, gid),
-            {
-                'submit': 'Confirm'
-            },
-            200,
-            [
-                b'was successfully added as a member to group'
-            ],
-            print_response
-        )
-        self.assertMailbox(
-            {
-                'subject': [
-                    '[{}] Account activation - {}'.format(self.app.config['APPLICATION_NAME'], uname)
-                ],
-                'sender': [
-                    'root@unittest'
-                ],
-                'recipients': [
-                    ['{}@bogus-domain.org'.format(uname)]
-                ],
-                'cc': [[]],
-                'bcc': [['admin@unittest']]
-            }
-        )
-        self.mailbox_clear()
-
-        # Additional test preparations.
-        with self.app.app_context():
-            uobj = self.user_get(uname)
-            gobj = self.group_get(gname)
-            uid = uobj.id
-            gid = gobj.id
-            uobj.enabled = False
-            uobj.memberships.remove(gobj)
-            uobj.memberships_wanted.append(gobj)
-            self.user_save(uobj)
-
-        #
-        # Check membership request rejection feature.
-        #
-        self.assertGetURL(
-            '/users/{}/reject_membership/{}'.format(uid, gid),
-            200,
-            [
-                b'Are you really sure you want to reject membership request of user'
-            ],
-            print_response
-        )
-        self.assertPostURL(
-            '/users/{}/reject_membership/{}'.format(uid, gid),
-            {
-                'submit': 'Confirm'
-            },
-            200,
-            [
-                b'was successfully rejected.'
-            ],
-            print_response
-        )
-        self.mailbox_monitoring('off')
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """Test access as user 'user'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_MAINTAINER,
-                hawat.const.ROLE_ADMIN
-            ):
-            self._attempt_fail(uname, vial.test.fixtures.DEMO_GROUP_A)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_02_as_developer(self):
-        """Test access as user 'developer'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_MAINTAINER,
-                hawat.const.ROLE_ADMIN
-            ):
-            self._attempt_succeed(uname, vial.test.fixtures.DEMO_GROUP_A)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_03_as_maintainer(self):
-        """Test access as user 'maintainer'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_ADMIN
-            ):
-            self._attempt_succeed(uname, vial.test.fixtures.DEMO_GROUP_A)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_04_as_admin(self):
-        """Test access as user 'admin'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_MAINTAINER
-            ):
-            self._attempt_succeed(uname, vial.test.fixtures.DEMO_GROUP_A)
-
-
-@unittest.skipIf(_IS_NOSE, "broken under nosetest")
-class UsersDeleteTestCase(UsersTestCaseMixin, TestRunnerMixin, VialTestCase):
-    """Class for testing ``users.delete`` endpoint."""
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_USER)
-    def test_01_as_user(self):
-        """Test access as user 'user'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_MAINTAINER,
-                hawat.const.ROLE_ADMIN
-            ):
-            self._attempt_fail_delete(uname)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_DEVELOPER)
-    def test_02_as_developer(self):
-        """Test access as user 'developer'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_MAINTAINER,
-                hawat.const.ROLE_ADMIN
-            ):
-            self._attempt_fail_delete(uname)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_MAINTAINER)
-    def test_03_as_maintainer(self):
-        """Test access as user 'maintainer'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_MAINTAINER,
-                hawat.const.ROLE_ADMIN
-            ):
-            self._attempt_fail_delete(uname)
-
-    @vial.test.do_as_user_decorator(hawat.const.ROLE_ADMIN)
-    def test_04_as_admin(self):
-        """Test access as user 'admin'."""
-        for uname in (
-                hawat.const.ROLE_USER,
-                hawat.const.ROLE_DEVELOPER,
-                hawat.const.ROLE_MAINTAINER
-            ):
-            self._attempt_succeed_delete(uname)
-
-
-#-------------------------------------------------------------------------------
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/lib/vial/blueprints/users/test/utils.py b/lib/vial/blueprints/users/test/utils.py
deleted file mode 100644
index 73188a1b8..000000000
--- a/lib/vial/blueprints/users/test/utils.py
+++ /dev/null
@@ -1,209 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-Unit test utilities for :py:mod:`vial.blueprints.users`.
-"""
-
-
-class UsersTestCaseMixin:
-    """Mixin class with unit test framework for testing user account management endpoints."""
-
-    def _attempt_fail_list(self):
-        """Check access to ``users.list`` endpoint and fail."""
-        self.assertGetURL(
-            '/users/list',
-            403
-        )
-
-    def _attempt_succeed_list(self, content_checks = None):
-        """Check access to ``users.list`` endpoint and succeed."""
-        if content_checks is None:
-            content_checks = [
-                b'Show details of user account &quot;user&quot;',
-                b'Show details of user account &quot;developer&quot;',
-                b'Show details of user account &quot;maintainer&quot;',
-                b'Show details of user account &quot;admin&quot;'
-            ]
-        self.assertGetURL(
-            '/users/list',
-            200,
-            content_checks
-        )
-
-    def _attempt_fail_show(self, uname):
-        """Check access to ``users.show`` endpoint and fail."""
-        uid = self.user_id(uname, with_app_ctx = True)
-        self.assertGetURL(
-            '/users/{}/show'.format(uid),
-            403
-        )
-
-    def _attempt_succeed_show(self, uname):
-        """Check access to ``users.show`` endpoint and succeed."""
-        with self.app.app_context():
-            uobj   = self.user_get(uname)
-            uid    = uobj.id
-            ufname = uobj.fullname
-        self.assertGetURL(
-            '/users/{}/show'.format(uid),
-            200,
-            [
-                '<h3>{} ({})</h3>'.format(ufname, uname).encode('utf8'),
-                b'<strong>Account created:</strong>'
-            ]
-        )
-
-    def _attempt_fail_create(self):
-        """Check access to ``users.create`` endpoint and fail."""
-        self.assertGetURL(
-            '/users/create',
-            403
-        )
-
-    def _attempt_succeed_create(self, data):
-        """Check access to ``users.create`` endpoint and succeed."""
-        self.assertCreate(
-            self.user_model(),
-            '/users/create',
-            data,
-            [
-                'User account <strong>{}</strong> was successfully created.'.format(data[0][1]).encode('utf8')
-            ]
-        )
-        self._attempt_succeed_show(
-            data[0][1]
-        )
-        self._attempt_succeed_list(
-            [
-                'Show details of user account &quot;{}&quot;'.format(data[0][1]).encode('utf8')
-            ]
-        )
-
-    def _attempt_fail_update(self, uname):
-        """Check access to ``users.update`` endpoint and fail."""
-        uid = self.user_id(uname, with_app_ctx = True)
-        self.assertGetURL(
-            '/users/{}/update'.format(uid),
-            403
-        )
-
-    def _attempt_succeed_update(self, uname):
-        """Check access to ``users.update`` endpoint and succeed."""
-        uid = self.user_id(uname, with_app_ctx = True)
-        self.assertGetURL(
-            '/users/{}/update'.format(uid),
-            200,
-            [
-                b'Update user account details'
-            ]
-        )
-
-    def _attempt_fail_enable(self, uname):
-        """Check access to ``users.enable`` endpoint and fail."""
-        uid = self.user_id(uname, with_app_ctx = True)
-        self.assertGetURL(
-            '/users/{}/enable'.format(uid),
-            403
-        )
-
-    def _attempt_succeed_enable(self, uname):
-        """Check access to ``users.enable`` endpoint and succeed."""
-        uid = self.user_id(uname, with_app_ctx = True)
-        self.mailbox_monitoring('on')
-        self.assertGetURL(
-            '/users/{}/enable'.format(uid),
-            200,
-            [
-                b'Are you really sure you want to enable following item:'
-            ]
-        )
-        self.assertPostURL(
-            '/users/{}/enable'.format(uid),
-            {
-                'submit': 'Confirm'
-            },
-            200,
-            [
-                b'was successfully enabled.'
-            ]
-        )
-        self.assertMailbox(
-            {
-                'subject': [
-                    '[{}] Account activation - {}'.format(self.app.config['APPLICATION_NAME'], uname)
-                ],
-                'sender': [
-                    'root@unittest'
-                ],
-                'recipients': [
-                    ['{}@bogus-domain.org'.format(uname)]
-                ],
-                'cc': [[]],
-                'bcc': [['admin@unittest']]
-            }
-        )
-        self.mailbox_monitoring('off')
-        self.mailbox_clear()
-
-    def _attempt_fail_disable(self, uname):
-        """Check access to ``users.disable`` endpoint and fail."""
-        uid = self.user_id(uname, with_app_ctx = True)
-        self.assertGetURL(
-            '/users/{}/disable'.format(uid),
-            403
-        )
-
-    def _attempt_succeed_disable(self, uname):
-        """Check access to ``users.disable`` endpoint and succeed."""
-        uid = self.user_id(uname, with_app_ctx = True)
-        self.assertGetURL(
-            '/users/{}/disable'.format(uid),
-            200,
-            [
-                b'Are you really sure you want to disable following item:'
-            ]
-        )
-        self.assertPostURL(
-            '/users/{}/disable'.format(uid),
-            {
-                'submit': 'Confirm'
-            },
-            200,
-            [
-                b'was successfully disabled.'
-            ]
-        )
-
-    def _attempt_fail_delete(self, uname):
-        """Check access to ``users.delete`` endpoint and fail."""
-        uid = self.user_id(uname, with_app_ctx = True)
-        self.assertGetURL(
-            '/users/{}/delete'.format(uid),
-            403
-        )
-
-    def _attempt_succeed_delete(self, uname):
-        """Check access to ``users.delete`` endpoint and succeed."""
-        uid = self.user_id(uname, with_app_ctx = True)
-        self.assertGetURL(
-            '/users/{}/delete'.format(uid),
-            200,
-            [
-                b'Are you really sure you want to permanently remove following item:'
-            ]
-        )
-        self.assertPostURL(
-            '/users/{}/delete'.format(uid),
-            {
-                'submit': 'Confirm'
-            },
-            200,
-            [
-                b'was successfully and permanently deleted.'
-            ]
-        )
diff --git a/lib/vial/test/__init__.py b/lib/vial/test/__init__.py
deleted file mode 100644
index 2c461b83a..000000000
--- a/lib/vial/test/__init__.py
+++ /dev/null
@@ -1,600 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-Base library for web interface unit tests.
-"""
-
-
-import io
-import unittest
-import pprint
-import logging
-import flask_mail
-import vial
-import vial.app
-import vial.db
-
-
-class do_as_user_decorator:  # pylint: disable=locally-disabled,invalid-name,too-few-public-methods
-    """
-    Decorator class for accessing application endpoints as given user.
-    """
-    def __init__(self, user_name, login_func_name = 'login_dev', login_func_params = None):
-        self.user_name         = user_name
-        self.login_func_name   = login_func_name
-        self.login_func_params = login_func_params or {}
-
-    def __call__(self, func):
-        def wrapped_f(other_self, *args, **kwargs):
-            login_func = getattr(other_self, self.login_func_name)
-            response = login_func(self.user_name, **self.login_func_params)
-            other_self.assertEqual(response.status_code, 200)
-
-            func(other_self, *args, **kwargs)
-
-            response = other_self.logout()
-            other_self.assertEqual(response.status_code, 200)
-
-        return wrapped_f
-
-
-def app_context_wrapper_decorator(func):
-    """
-    Decorator class for conditional wrapping of given function with application context.
-    """
-    def wrapped_f(self, *args, **kwargs):
-        if 'with_app_ctx' not in kwargs or not kwargs['with_app_ctx']:
-            return func(self, *args, **kwargs)
-        with self.app.app_context():
-            return func(self, *args, **kwargs)
-
-    return wrapped_f
-
-
-class VialTestCase(unittest.TestCase):
-    """
-    Class for testing :py:class:`vial.app.Vial` application.
-    """
-
-    logger = logging.getLogger()
-    logger.level = logging.DEBUG
-
-    def on_email_sent(self, message, app):  # pylint: disable=locally-disabled,unused-argument
-        """
-        Signal handler for handling :py:func:`flask_mail.email_dispatched` signal.
-        Log subject and recipients of all emails that have been sent.
-        """
-        #pprint.pprint(message)
-        #app.logger.info(
-        #    "Sent email '%s' to '%s'",
-        #    message.subject,
-        #    ', '.join(message.recipients)
-        #)
-        self.mailbox.append(message)
-
-    #--------------------------------------------------------------------------
-
-    def setUp(self):
-        self.setup_logging()
-        self.mailbox = []
-        self.fixtures_db = []
-
-        self.app = self.setup_app()
-        self.client = self.app.test_client()
-        self.setup_db()
-
-    def setup_logging(self):
-        """
-        Setup logging configuration for testing purposes.
-        """
-        #for hdlr in self.logger.handlers:
-        #    self.logger.removeHandler(hdlr)
-        self.loghdlr = logging.StreamHandler(io.StringIO())
-        self.logger.addHandler(self.loghdlr)
-
-    def setup_app(self):
-        """
-        Setup application object.
-        """
-        raise NotImplementedError()
-
-    def setup_db(self):
-        """
-        Perform database setup.
-        """
-        with self.app.app_context():
-            vial.db.db_get().drop_all()
-            vial.db.db_get().create_all()
-            self.setup_fixtures_db()
-
-    def get_fixtures_db(self, app):
-        raise NotImplementedError()
-
-    def setup_fixtures_db(self):
-        """
-        Setup general database object fixtures.
-        """
-        fixture_list = self.get_fixtures_db(self.app)
-        for dbobject in fixture_list:
-            vial.db.db_session().add(dbobject)
-            vial.db.db_session().commit()
-        self.fixtures_db.extend(fixture_list)
-
-    #--------------------------------------------------------------------------
-
-    def tearDown(self):
-        self.teardown_logging()
-        self.teardown_db()
-
-    def teardown_logging(self):
-        """
-        Teardown logging configuration for testing purposes.
-        """
-        #print(
-        #    "CAPTURED LOG CONTENTS:\n{}".format(
-        #        self.loghdlr.stream.getvalue()
-        #    )
-        #)
-        self.loghdlr.stream.close()
-        self.logger.removeHandler(self.loghdlr)
-
-    def teardown_db(self):
-        with self.app.app_context():
-            vial.db.db_get().drop_all()
-
-    #--------------------------------------------------------------------------
-
-    def login_dev(self, login):
-        """
-        Login given user with *auth_dev* module.
-        """
-        return self.client.post(
-            '/auth_dev/login',
-            data = dict(login = login, submit = 'Login'),
-            follow_redirects = True
-        )
-
-    def login_pwd(self, login, password):
-        """
-        Login given user with *auth_pwd* module.
-        """
-        return self.client.post(
-            '/auth_pwd/login',
-            data = dict(login = login, password = password, submit = 'Login'),
-            follow_redirects = True
-        )
-
-    def login_env(self, login, envvar = 'eppn'):
-        """
-        Login given user with *auth_env* module.
-        """
-        return self.client.get(
-            '/auth_env/login',
-            environ_base = {envvar: login},
-            follow_redirects = True
-        )
-
-    def logout(self):
-        """
-        Logout current user.
-        """
-        return self.client.get(
-            '/logout',
-            follow_redirects = True
-        )
-
-    #--------------------------------------------------------------------------
-
-    def log_get(self):
-        """
-        Get content written to log so far.
-        """
-        return self.loghdlr.stream.getvalue()
-
-    def log_clear(self):
-        """
-        Clear log content.
-        """
-        self.loghdlr.stream.close()
-        self.loghdlr.stream = io.StringIO()
-
-    #--------------------------------------------------------------------------
-
-    def mailbox_clear(self):
-        """
-        Clear internal mailbox.
-        """
-        self.mailbox = []
-
-    def mailbox_monitoring(self, state):
-        """
-        Enable/disable mailbox monitoring.
-        """
-        if state == 'on':
-            flask_mail.email_dispatched.connect(self.on_email_sent)
-            return
-
-        if state == 'off':
-            flask_mail.email_dispatched.disconnect(self.on_email_sent)
-            return
-
-        raise ValueError(
-            'Invalid parameter for mailbox_monitoring, must be "on" or "off", received {}'.format(
-                str(state)
-            )
-        )
-
-    #--------------------------------------------------------------------------
-
-    def user_model(self):
-        """
-        Get user model class.
-        """
-        return self.app.get_model(hawat.const.MODEL_USER)
-
-    @app_context_wrapper_decorator
-    def user_get(self, user_name, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        """
-        Get user object according to given user name from database.
-        """
-        user_model = self.user_model()
-        return vial.db.db_session().query(user_model).filter(user_model.login == user_name).one_or_none()
-
-    @app_context_wrapper_decorator
-    def user_save(self, user_object, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        """
-        Update given user object within database.
-        """
-        vial.db.db_session().add(user_object)
-        vial.db.db_session().commit()
-
-    @app_context_wrapper_decorator
-    def user_id(self, user_name, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        """
-        Get ID of user with given name within database.
-        """
-        uobj = self.user_get(user_name)
-        return uobj.id
-
-    @app_context_wrapper_decorator
-    def user_enabled(self, user_name, state, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        """
-        Enable/disable given user within database.
-        """
-        user = self.user_get(user_name)
-        user.enabled = state
-        self.user_save(user)
-
-    #--------------------------------------------------------------------------
-
-    def group_model(self):
-        """
-        Get user model class.
-        """
-        return self.app.get_model(hawat.const.MODEL_GROUP)
-
-    @app_context_wrapper_decorator
-    def group_get(self, group_name, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        """
-        Get group object according to given group name within database.
-        """
-        group_model = self.group_model()
-        return vial.db.db_session().query(group_model).filter(group_model.name == group_name).one_or_none()
-
-    @app_context_wrapper_decorator
-    def group_save(self, group_object, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        """
-        Update given group object within database.
-        """
-        vial.db.db_session().add(group_object)
-        vial.db.db_session().commit()
-
-    @app_context_wrapper_decorator
-    def group_id(self, group_name, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        """
-        Get ID of given group within database.
-        """
-        gobj = self.group_get(group_name)
-        return gobj.id
-
-    @app_context_wrapper_decorator
-    def group_enabled(self, group_name, state, **kwargs):  # pylint: disable=locally-disabled,unused-argument
-        """
-        Enable/disable given group within database.
-        """
-        group = self.group_get(group_name)
-        group.enabled = state
-        self.group_save(group)
-
-    #--------------------------------------------------------------------------
-
-    def assertGetURL(self, url, status_code = 200, content_checks = None, print_response = False, follow_redirects = True):  # pylint: disable=locally-disabled,invalid-name
-        """
-        Perform GET request and check some default assertions against the response.
-        """
-        response = self.client.get(
-            url,
-            follow_redirects = follow_redirects
-        )
-        if print_response:
-            print("--------------------------------------------------------------------------------")
-            print("Response for GET {}: {} ({})".format(url, response.status_code, response.status))
-            pprint.pprint(response.headers)
-            pprint.pprint(response.data)
-            print("--------------------------------------------------------------------------------")
-        self.assertEqual(response.status_code, status_code)
-        if content_checks:
-            for cch in content_checks:
-                self.assertTrue(cch in response.data)
-        return response
-
-    def assertPostURL(self, url, data, status_code = 200, content_checks = None, print_response = False, follow_redirects = True):  # pylint: disable=locally-disabled,invalid-name
-        """
-        Perform POST request and check some default assertions against the response.
-        """
-        response = self.client.post(
-            url,
-            data = data,
-            follow_redirects = follow_redirects
-        )
-        if print_response:
-            print("--------------------------------------------------------------------------------")
-            print("Response for POST {}, {}: {} ({})".format(url, pprint.pformat(data), response.status_code, response.status))
-            pprint.pprint(response.headers)
-            pprint.pprint(response.data)
-            print("--------------------------------------------------------------------------------")
-        self.assertEqual(response.status_code, status_code)
-        if content_checks:
-            for cch in content_checks:
-                self.assertTrue(cch in response.data)
-        return response
-
-    def assertMailbox(self, checklist):  # pylint: disable=locally-disabled,invalid-name
-        """
-        Check internal mailbox.
-        """
-        for attr_name in ('subject', 'sender', 'recipients', 'cc', 'bcc', 'body', 'html'):
-            if attr_name in checklist:
-                self.assertEqual(
-                    list(
-                        map(
-                            lambda x: getattr(x, attr_name),
-                            self.mailbox,
-                        )
-                    ),
-                    checklist[attr_name]
-                )
-
-class ItemCreateVialTestCase(VialTestCase):
-    """
-    Class for testing :py:class:`vial.app.Vial` application item creation views.
-    """
-    maxDiff = None
-
-    def assertCreate(self, item_model, url, data, content_checks = None, print_response = False):  # pylint: disable=locally-disabled,invalid-name
-        """
-        Perform attempt to create given item.
-        """
-
-        # Verify, that the item form correctly displays.
-        response = self.assertGetURL(
-            url,
-            200,
-            [
-                '<form method="POST" action="{}" id="form-{}-create'.format(
-                    url,
-                    item_model.__name__.lower()
-                ).encode('utf8'),
-                b'<div class="btn-toolbar" role="toolbar" aria-label="Form submission buttons">'
-            ],
-            print_response = print_response
-        )
-
-        # Attempt to send empty item form. There is always at least one mandatory
-        # form field, so we should get some "This field is required." error.
-        request_data = {'submit': 'Submit'}
-        response = self.assertPostURL(
-            url,
-            request_data,
-            200,
-            [
-                b'This field is required.',
-                b'help-block form-error'
-            ],
-            print_response = print_response
-        )
-
-        # Attempt to send form with some mandatory fields missing.
-        #for idx, param in enumerate(data):
-        #    if idx == len(data) - 1:
-        #        break
-        #    response = self.client.post(
-        #        url,
-        #        follow_redirects = True,
-        #        data = {
-        #            i[0]: i[1] for i in data[0:idx+1]
-        #        }
-        #    )
-        #    self.assertEqual(response.status_code, 200)
-        #    self.assertTrue(b'This field is required.' in response.data)
-        #    self.assertTrue(b'help-block form-error' in response.data)
-
-        # Attempt to send form with valid data.
-        request_data = {
-            i[0]: i[1] for i in data
-        }
-        request_data['submit'] = 'Submit'
-        response = self.assertPostURL(
-            url,
-            request_data,
-            200,
-            [
-                b'<div class="alert alert-success alert-dismissible">'
-            ],
-            print_response = print_response
-        )
-        if content_checks:
-            for cch in content_checks:
-                self.assertTrue(cch in response.data)
-        return response
-
-
-class RegistrationVialTestCase(VialTestCase):
-    """
-    Class for testing :py:class:`vial.app.Vial` application registration views.
-    """
-    maxDiff = None
-
-    user_fixture = {
-        'apikey': None,
-        'email': 'test.user@domain.org',
-        'enabled': False,
-        'fullname': 'Test User',
-        'id': 5,
-        'locale': None,
-        'login': 'test',
-        'logintime': 'None',
-        'managements': [],
-        'memberships': [],
-        'memberships_wanted': [],
-        'roles': ['user'],
-        'timezone': None
-    }
-
-    def assertRegisterFail(self, url, data, environ_base = None):  # pylint: disable=locally-disabled,invalid-name
-        response = response = self.client.get(
-            url,
-            follow_redirects = True,
-            environ_base = environ_base,
-        )
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'User account registration' in response.data)
-
-        for idx, param in enumerate(data):
-            if idx == len(data) - 1:
-                break
-            response = response = self.client.post(
-                url,
-                follow_redirects = True,
-                environ_base = environ_base,
-                data = {
-                    i[0]: i[1] for i in data[0:idx+1]
-                }
-            )
-            self.assertEqual(response.status_code, 200)
-            self.assertTrue(b'This field is required.' in response.data)
-            self.assertTrue(b'help-block form-error' in response.data)
-
-        response = response = self.client.post(
-            url,
-            follow_redirects = True,
-            environ_base = environ_base,
-            data = {
-                i[0]: i[1] for i in data
-            }
-        )
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(
-            b'Please use different login, the &#34;user&#34; is already taken.' in response.data or \
-            b'Please use different login, the "user" is already taken.' in response.data
-        )
-
-
-    def assertRegister(self, url, data, emails, environ_base = None):  # pylint: disable=locally-disabled,invalid-name
-        uname = 'test'
-        self.mailbox_monitoring('on')
-
-        response = response = self.client.get(
-            url,
-            follow_redirects = True,
-            environ_base = environ_base
-        )
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'User account registration' in response.data)
-
-        for idx, param in enumerate(data):
-            if idx == len(data) - 1:
-                break
-            response = response = self.client.post(
-                url,
-                follow_redirects = True,
-                environ_base = environ_base,
-                data = {
-                    i[0]: i[1] for i in data[0:idx+1]
-                }
-            )
-            self.assertEqual(response.status_code, 200)
-            self.assertTrue(b'This field is required.' in response.data)
-            self.assertTrue(b'help-block form-error' in response.data)
-
-        response = response = self.client.post(
-            url,
-            follow_redirects = True,
-            environ_base = environ_base,
-            data = {
-                i[0]: i[1] for i in data
-            }
-        )
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'User account <strong>test (Test User)</strong> was successfully registered.' in response.data)
-        with self.app.app_context():
-            uobj = self.user_get(uname)
-            self.assertTrue(uobj)
-        self.assertMailbox(
-            {
-                'subject': [
-                    '[{}] Account registration - {}'.format(self.app.config['APPLICATION_NAME'], uname),
-                    '[{}] Account registration - {}'.format(self.app.config['APPLICATION_NAME'], uname)
-                ],
-                'sender': [
-                    'root@unittest',
-                    'root@unittest'
-                ],
-                'recipients': [
-                    ['admin@unittest'],
-                    ['test.user@domain.org']
-                ],
-                'cc': [[],[]],
-                'bcc': [[], ['admin@unittest']],
-                'body': emails['txt'],
-                'html': emails['html']
-            }
-        )
-
-        self.mailbox_monitoring('off')
-
-        with self.app.app_context():
-            user = self.user_get(uname)
-            user_dict = user.to_dict()
-            del user_dict['createtime']
-            del user_dict['password']
-            self.assertEqual(
-                user_dict,
-                self.user_fixture
-            )
-        response = self.login_dev(uname)
-        self.assertEqual(response.status_code, 403)
-        #self.assertTrue(b'is currently disabled, you are not permitted to log in.' in response.data)
-
-        with self.app.app_context():
-            user = self.user_get(uname)
-            user.set_state_enabled()
-            self.user_save(user)
-
-        with self.app.app_context():
-            user = self.user_get(uname)
-            user_dict = user.to_dict()
-            del user_dict['createtime']
-            del user_dict['password']
-        response = self.login_dev(uname)
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged in as' in response.data)
-
-        response = self.logout()
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(b'You have been successfully logged out' in response.data)
diff --git a/lib/vial/test/runner.py b/lib/vial/test/runner.py
deleted file mode 100644
index bf8c3a02f..000000000
--- a/lib/vial/test/runner.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-
-"""
-Base library for web interface unit tests.
-"""
-
-
-import vial
-import vial.app
-import vial.db
-import vial.model.db
-import vial.test.fixtures
-
-
-#logging.disable(logging.CRITICAL+1000)
-
-
-def _config_testapp_vial(app_config):
-    """
-    Configure and reconfigure application for testing before instantination.
-    """
-
-    # Customize configurations for testing purposes.
-    app_config['TESTING'] = True
-    app_config['WTF_CSRF_ENABLED'] = False
-    app_config['DEBUG'] = False
-    app_config['EXPLAIN_TEMPLATE_LOADING'] = False
-    app_config['LOG_FILE'] = '/var/tmp/vial-utest.log'
-    app_config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://mentat:mentat@localhost/mentat_utest'
-    app_config['MAIL_SERVER'] = 'localhost'
-    app_config['MAIL_PORT'] = 1025
-    app_config['MAIL_DEFAULT_SENDER'] = 'root@unittest'
-    app_config['EMAIL_ADMINS'] = ['admin@unittest']
-    app_config['DISABLE_MAIL_LOGGING'] = True
-    app_config['MODELS'] = {
-        hawat.const.MODEL_USER: vial.model.db.UserModel,
-        hawat.const.MODEL_GROUP: vial.model.db.GroupModel,
-        hawat.const.MODEL_ITEM_CHANGELOG: vial.model.db.ItemChangeLogModel
-    }
-
-
-class TestRunnerMixin:
-    def setup_app(self):
-        """
-        Setup application object.
-        """
-        return vial.create_app_full(
-            vial.app.Vial,
-            'vial',
-            config_object = 'vial.config.TestingConfig',
-            config_func = _config_testapp_vial
-        )
-
-    def get_fixtures_db(self, app):
-        return vial.test.fixtures.get_fixtures_db(app)
-- 
GitLab