From 7ec2d479d043257c1144c92f858d5693ccec468f Mon Sep 17 00:00:00 2001
From: Jan Mach <jan.mach@cesnet.cz>
Date: Thu, 19 Mar 2020 20:13:52 +0100
Subject: [PATCH] Implemented new base view vial.viev.BaseRegistrationView for
 all user registration views.

(Redmine issue: #4410,#3443,#1017)
---
 lib/hawat/blueprints/auth_dev/__init__.py     | 141 +--------------
 .../templates/registration/email_admins.txt   |  27 +++
 .../templates/registration/email_managers.txt |  20 +++
 .../templates/registration/email_user.txt     |  31 ++++
 .../blueprints/auth_dev/test/__init__.py      |  60 ++++++-
 .../templates/registration/email_admins.txt   |  27 +++
 .../templates/registration/email_managers.txt |  20 +++
 .../templates/registration/email_user.txt     |  31 ++++
 lib/vial/test/__init__.py                     |   9 +-
 lib/vial/view/__init__.py                     | 168 +++++++++++++++++-
 10 files changed, 388 insertions(+), 146 deletions(-)
 create mode 100644 lib/hawat/blueprints/auth_dev/templates/registration/email_admins.txt
 create mode 100644 lib/hawat/blueprints/auth_dev/templates/registration/email_managers.txt
 create mode 100644 lib/hawat/blueprints/auth_dev/templates/registration/email_user.txt
 create mode 100644 lib/hawat/templates/registration/email_admins.txt
 create mode 100644 lib/hawat/templates/registration/email_managers.txt
 create mode 100644 lib/hawat/templates/registration/email_user.txt

diff --git a/lib/hawat/blueprints/auth_dev/__init__.py b/lib/hawat/blueprints/auth_dev/__init__.py
index 979b7060..bf47e934 100644
--- a/lib/hawat/blueprints/auth_dev/__init__.py
+++ b/lib/hawat/blueprints/auth_dev/__init__.py
@@ -42,21 +42,14 @@ __author__ = "Jan Mach <jan.mach@cesnet.cz>"
 __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
 
 
-import sys
-import traceback
-import sqlalchemy
-
 import flask
-import flask_login
-import flask_principal
-import flask_mail
-from flask_babel import gettext, lazy_gettext, force_locale
+from flask_babel import gettext, lazy_gettext
 
 import vial.const
 import vial.forms
 import vial.db
 from vial.app import VialBlueprint
-from vial.view import ItemCreateView, BaseLoginView
+from vial.view import BaseLoginView, BaseRegisterView
 from vial.view.mixin import HTMLMixin, SQLAlchemyMixin
 
 from hawat.blueprints.auth_dev.forms import LoginForm, RegisterUserAccountForm
@@ -72,6 +65,8 @@ class LoginView(HTMLMixin, SQLAlchemyMixin, BaseLoginView):
     """
     methods = ['GET', 'POST']
 
+    is_sign_in = True
+
     @classmethod
     def get_view_title(cls, **kwargs):
         return lazy_gettext('Developer login')
@@ -98,20 +93,12 @@ class LoginView(HTMLMixin, SQLAlchemyMixin, BaseLoginView):
         return None
 
 
-class RegisterView(HTMLMixin, SQLAlchemyMixin, ItemCreateView):
+class RegisterView(HTMLMixin, SQLAlchemyMixin, BaseRegisterView):
     """
     View responsible for registering new user account into application.
     """
     methods = ['GET', 'POST']
 
-    @classmethod
-    def get_view_name(cls):
-        return 'register'
-
-    @classmethod
-    def get_view_icon(cls):
-        return 'register'
-
     @classmethod
     def get_menu_title(cls, **kwargs):
         return lazy_gettext('Register (dev)')
@@ -120,10 +107,6 @@ class RegisterView(HTMLMixin, SQLAlchemyMixin, ItemCreateView):
     def get_view_title(cls, **kwargs):
         return lazy_gettext('User account registration (dev)')
 
-    @classmethod
-    def get_view_template(cls):
-        return '{}/registration.html'.format(BLUEPRINT_NAME)
-
     @property
     def dbmodel(self):
         return self.get_model(vial.const.MODEL_USER)
@@ -149,120 +132,6 @@ class RegisterView(HTMLMixin, SQLAlchemyMixin, ItemCreateView):
             choices_locales = locales
         )
 
-    @staticmethod
-    def get_message_success(**kwargs):
-        return gettext(
-            'User account <strong>%(login)s (%(name)s)</strong> was successfully registered.',
-            login = kwargs['item'].login,
-            name = kwargs['item'].fullname
-        )
-
-    @staticmethod
-    def get_message_failure(**kwargs):
-        return gettext('Unable to register new user account.')
-
-    @staticmethod
-    def get_message_cancel(**kwargs):
-        return gettext('Account registration canceled.')
-
-    @classmethod
-    def inform_admins(cls, account, form_data):
-        """
-        Send information about new account registration to system
-        admins. Use default locale for email content translations.
-        """
-        mail_locale = flask.current_app.config['BABEL_DEFAULT_LOCALE']
-        with force_locale(mail_locale):
-            msg = flask_mail.Message(
-                gettext(
-                    "[%(app_name)s] Account registration - %(item_id)s",
-                    app_name = flask.current_app.config['APPLICATION_NAME'],
-                    item_id = account.login
-                ),
-                recipients = flask.current_app.config['EMAIL_ADMINS']
-            )
-            msg.body = flask.render_template(
-                'auth_env/email_registration_admins.txt',
-                account = account,
-                justification = form_data['justification']
-            )
-            flask.current_app.mailer.send(msg)
-
-    @classmethod
-    def inform_managers(cls, account, form_data):
-        """
-        Send information about new account registration to the user.
-        Use manager`s locale for email content translations.
-        """
-        for group in account.memberships_wanted:
-            if not group.managed:
-                return
-
-            if not group.managers:
-                flask.current_app.logger.error(
-                    "Group '{}' is marked as self-managed, but there are no managers.".format(
-                        group.name
-                    )
-                )
-                return
-
-            for manager in group.managers:
-                mail_locale = manager.locale
-                if not mail_locale or mail_locale == 'None':
-                    mail_locale = flask.current_app.config['BABEL_DEFAULT_LOCALE']
-                with force_locale(mail_locale):
-                    msg = flask_mail.Message(
-                        gettext(
-                            "[%(app_name)s] Account registration - %(item_id)s",
-                            app_name = flask.current_app.config['APPLICATION_NAME'],
-                            item_id = account.login
-                        ),
-                        recipients = [manager.email],
-                        bcc = flask.current_app.config['EMAIL_ADMINS']
-                    )
-                    msg.body = flask.render_template(
-                        'auth_env/email_registration_managers.txt',
-                        account = account,
-                        group = group,
-                        justification = form_data['justification']
-                    )
-                    flask.current_app.mailer.send(msg)
-
-    @classmethod
-    def inform_user(cls, account, form_data):
-        """
-        Send information about new account registration to the user.
-        Use user`s preferred locale for email content translations.
-        """
-        mail_locale = account.locale
-        if not mail_locale or mail_locale == 'None':
-            mail_locale = flask.current_app.config['BABEL_DEFAULT_LOCALE']
-        with force_locale(mail_locale):
-            msg = flask_mail.Message(
-                gettext(
-                    "[%(app_name)s] Account registration - %(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(
-                'auth_env/email_registration_user.txt',
-                account = account,
-                justification = form_data['justification']
-            )
-            flask.current_app.mailer.send(msg)
-
-    def do_before_action(self, item):  # pylint: disable=locally-disabled,no-self-use,unused-argument
-        item.roles = [vial.const.ROLE_USER]
-        item.enabled = False
-
-    def do_after_action(self, item):  # pylint: disable=locally-disabled,no-self-use,unused-argument
-        self.inform_admins(item, self.response_context['form_data'])
-        self.inform_managers(item, self.response_context['form_data'])
-        self.inform_user(item, self.response_context['form_data'])
-
 
 #-------------------------------------------------------------------------------
 
diff --git a/lib/hawat/blueprints/auth_dev/templates/registration/email_admins.txt b/lib/hawat/blueprints/auth_dev/templates/registration/email_admins.txt
new file mode 100644
index 00000000..4ec6751c
--- /dev/null
+++ b/lib/hawat/blueprints/auth_dev/templates/registration/email_admins.txt
@@ -0,0 +1,27 @@
+{{ _('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 }}
+    {{ '{:16s}'.format(_('Organization:')) }} {{ account.organization }}
+{%- 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/hawat/blueprints/auth_dev/templates/registration/email_managers.txt b/lib/hawat/blueprints/auth_dev/templates/registration/email_managers.txt
new file mode 100644
index 00000000..c3b79bb7
--- /dev/null
+++ b/lib/hawat/blueprints/auth_dev/templates/registration/email_managers.txt
@@ -0,0 +1,20 @@
+{{ _('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 }}
+    {{ '{:16s}'.format(_('Organization:')) }} {{ account.organization }}
+
+{{ _('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/hawat/blueprints/auth_dev/templates/registration/email_user.txt b/lib/hawat/blueprints/auth_dev/templates/registration/email_user.txt
new file mode 100644
index 00000000..0ced73c5
--- /dev/null
+++ b/lib/hawat/blueprints/auth_dev/templates/registration/email_user.txt
@@ -0,0 +1,31 @@
+{{ _('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 }}
+    {{ '{:16s}'.format(_('Organization:')) }} {{ account.organization }}
+{%- 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_pwd.login', _external = True ) }}
+
+{{ _('Have a nice day') | wordwrap }}
+
+-- {{ vial_appname }}
diff --git a/lib/hawat/blueprints/auth_dev/test/__init__.py b/lib/hawat/blueprints/auth_dev/test/__init__.py
index 582e349b..abe22de9 100644
--- a/lib/hawat/blueprints/auth_dev/test/__init__.py
+++ b/lib/hawat/blueprints/auth_dev/test/__init__.py
@@ -75,7 +75,65 @@ class AuthDevTestCase(RegistrationTestCase):
                 ('email', 'test.user@domain.org'),
                 ('organization', 'TEST, org.'),
                 ('justification', 'I really want in.')
-            ]
+            ],
+            {
+                'txt': [
+                    'Dear administrator,\n'
+                    '\n'
+                    'a new account "test" was just registered in Mentat. 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'
+                    '    Organization:    TEST, 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'
+                    '-- Mentat',
+                    'Dear user,\n'
+                    '\n'
+                    'this email is a confirmation, that you have successfully registered your '
+                    'new\n'
+                    'user account "test" in Mentat.\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'
+                    '    Organization:    TEST, 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_pwd/login\n'
+                    '\n'
+                    'Have a nice day\n'
+                    '\n'
+                    '-- Mentat'
+                ],
+                'html': [
+                    None,
+                    None
+                ]
+            }
         )
 
     def test_05_register_fail(self):
diff --git a/lib/hawat/templates/registration/email_admins.txt b/lib/hawat/templates/registration/email_admins.txt
new file mode 100644
index 00000000..4ec6751c
--- /dev/null
+++ b/lib/hawat/templates/registration/email_admins.txt
@@ -0,0 +1,27 @@
+{{ _('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 }}
+    {{ '{:16s}'.format(_('Organization:')) }} {{ account.organization }}
+{%- 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/hawat/templates/registration/email_managers.txt b/lib/hawat/templates/registration/email_managers.txt
new file mode 100644
index 00000000..c3b79bb7
--- /dev/null
+++ b/lib/hawat/templates/registration/email_managers.txt
@@ -0,0 +1,20 @@
+{{ _('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 }}
+    {{ '{:16s}'.format(_('Organization:')) }} {{ account.organization }}
+
+{{ _('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/hawat/templates/registration/email_user.txt b/lib/hawat/templates/registration/email_user.txt
new file mode 100644
index 00000000..0ced73c5
--- /dev/null
+++ b/lib/hawat/templates/registration/email_user.txt
@@ -0,0 +1,31 @@
+{{ _('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 }}
+    {{ '{:16s}'.format(_('Organization:')) }} {{ account.organization }}
+{%- 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_pwd.login', _external = True ) }}
+
+{{ _('Have a nice day') | wordwrap }}
+
+-- {{ vial_appname }}
diff --git a/lib/vial/test/__init__.py b/lib/vial/test/__init__.py
index abbfc36c..1aec1c7d 100644
--- a/lib/vial/test/__init__.py
+++ b/lib/vial/test/__init__.py
@@ -266,7 +266,7 @@ class VialTestCase(unittest.TestCase):
         """
         Check internal mailbox.
         """
-        for attr_name in ('subject', 'sender', 'recipients', 'cc', 'bcc'):
+        for attr_name in ('subject', 'sender', 'recipients', 'cc', 'bcc', 'body', 'html'):
             self.assertEqual(
                 list(
                     map(
@@ -281,6 +281,7 @@ class RegistrationVialTestCase(VialTestCase):
     """
     Class for testing :py:class:`vial.app.Vial` application registration views.
     """
+    maxDiff = None
 
     def assertRegisterFail(self, url, data):  # pylint: disable=locally-disabled,invalid-name
         response = response = self.client.get(
@@ -315,7 +316,7 @@ class RegistrationVialTestCase(VialTestCase):
         self.assertTrue(b'Please use different login, the &#34;user&#34; is already taken.' in response.data)
 
 
-    def assertRegister(self, url, data):  # pylint: disable=locally-disabled,invalid-name
+    def assertRegister(self, url, data, emails):  # pylint: disable=locally-disabled,invalid-name
         uname = 'test'
         self.mailbox_monitoring('on')
 
@@ -367,7 +368,9 @@ class RegistrationVialTestCase(VialTestCase):
                     ['test.user@domain.org']
                 ],
                 'cc': [[],[]],
-                'bcc': [[], ['admin@unittest']]
+                'bcc': [[], ['admin@unittest']],
+                'body': emails['txt'],
+                'html': emails['html']
             }
         )
 
diff --git a/lib/vial/view/__init__.py b/lib/vial/view/__init__.py
index 2510ffe2..e4ef40da 100644
--- a/lib/vial/view/__init__.py
+++ b/lib/vial/view/__init__.py
@@ -24,7 +24,8 @@ import flask.app
 import flask.views
 import flask_login
 import flask_principal
-from flask_babel import gettext
+import flask_mail
+from flask_babel import gettext, force_locale
 
 import vial.const
 import vial.menu
@@ -1320,6 +1321,12 @@ class ItemCreateView(ItemActionView):  # pylint: disable=locally-disabled,abstra
         """*Implementation* of :py:func:`vial.view.BaseView.get_menu_title`."""
         return gettext('Create')
 
+    def get_item(self):
+        """
+        *Hook method*. Must return instance for given item class.
+        """
+        return self.dbmodel()
+
     @staticmethod
     def get_item_form(item):
         """
@@ -1340,8 +1347,11 @@ class ItemCreateView(ItemActionView):  # pylint: disable=locally-disabled,abstra
         if not self.authorize_item_action():
             self.abort(403)
 
-        item = self.dbmodel()
+        item = self.get_item()
+        self.response_context.update(item = item)
+
         form = self.get_item_form(item)
+        self.response_context.update(form = form)
 
         cancel_response = self.check_action_cancel(form)
         if cancel_response:
@@ -1384,9 +1394,7 @@ class ItemCreateView(ItemActionView):  # pylint: disable=locally-disabled,abstra
         self.response_context.update(
             action_name = gettext('Create'),
             form_url    = flask.url_for(self.get_view_endpoint()),
-            form        = form,
-            item_action = vial.const.ACTION_ITEM_CREATE,
-            item        = item
+            item_action = vial.const.ACTION_ITEM_CREATE
         )
 
         self.do_before_response()
@@ -1443,6 +1451,12 @@ class ItemCreateForView(ItemActionView):  # pylint: disable=locally-disabled,abs
         """
         return self.dbsession.query(self.dbmodel_par)
 
+    def get_item(self):
+        """
+        *Hook method*. Must return instance for given item class.
+        """
+        return self.dbmodel()
+
     @staticmethod
     def get_item_form(item):
         """
@@ -1482,7 +1496,7 @@ class ItemCreateForView(ItemActionView):  # pylint: disable=locally-disabled,abs
             parent = parent
         )
 
-        item = self.dbmodel()
+        item = self.get_item()
         form = self.get_item_form(item)
 
         cancel_response = self.check_action_cancel(form, parent = parent)
@@ -1536,6 +1550,148 @@ class ItemCreateForView(ItemActionView):  # pylint: disable=locally-disabled,abs
         return self.generate_response()
 
 
+class BaseRegisterView(ItemCreateView):  # pylint: disable=locally-disabled,abstract-method
+    """
+    View responsible for registering new user account into application.
+    """
+    methods = ['GET', 'POST']
+
+    @classmethod
+    def get_view_name(cls):
+        return 'register'
+
+    @classmethod
+    def get_view_icon(cls):
+        return 'register'
+
+    @classmethod
+    def get_menu_title(cls, **kwargs):
+        return gettext('Register')
+
+    @classmethod
+    def get_view_title(cls, **kwargs):
+        return gettext('User account registration')
+
+    @classmethod
+    def get_view_template(cls):
+        return '{}/registration.html'.format(cls.module_name)
+
+    @staticmethod
+    def get_message_success(**kwargs):
+        return gettext(
+            'User account <strong>%(login)s (%(name)s)</strong> was successfully registered.',
+            login = kwargs['item'].login,
+            name = kwargs['item'].fullname
+        )
+
+    @staticmethod
+    def get_message_failure(**kwargs):
+        return gettext('Unable to register new user account.')
+
+    @staticmethod
+    def get_message_cancel(**kwargs):
+        return gettext('Account registration canceled.')
+
+    @classmethod
+    def inform_admins(cls, account, form_data):
+        """
+        Send information about new account registration to system
+        admins. Use default locale for email content translations.
+        """
+        mail_locale = flask.current_app.config['BABEL_DEFAULT_LOCALE']
+        with force_locale(mail_locale):
+            msg = flask_mail.Message(
+                gettext(
+                    "[%(app_name)s] Account registration - %(item_id)s",
+                    app_name = flask.current_app.config['APPLICATION_NAME'],
+                    item_id = account.login
+                ),
+                recipients = flask.current_app.config['EMAIL_ADMINS']
+            )
+            msg.body = flask.render_template(
+                'registration/email_admins.txt',
+                account = account,
+                justification = form_data['justification']
+            )
+            flask.current_app.mailer.send(msg)
+
+    @classmethod
+    def inform_managers(cls, account, form_data):
+        """
+        Send information about new account registration to the user.
+        Use manager`s locale for email content translations.
+        """
+        for group in account.memberships_wanted:
+            if not group.managed:
+                return
+
+            if not group.managers:
+                flask.current_app.logger.error(
+                    "Group '{}' is marked as self-managed, but there are no managers.".format(
+                        group.name
+                    )
+                )
+                return
+
+            for manager in group.managers:
+                mail_locale = manager.locale
+                if not mail_locale or mail_locale == 'None':
+                    mail_locale = flask.current_app.config['BABEL_DEFAULT_LOCALE']
+                with force_locale(mail_locale):
+                    msg = flask_mail.Message(
+                        gettext(
+                            "[%(app_name)s] Account registration - %(item_id)s",
+                            app_name = flask.current_app.config['APPLICATION_NAME'],
+                            item_id = account.login
+                        ),
+                        recipients = [manager.email],
+                        bcc = flask.current_app.config['EMAIL_ADMINS']
+                    )
+                    msg.body = flask.render_template(
+                        'registration/email_managers.txt',
+                        account = account,
+                        group = group,
+                        justification = form_data['justification']
+                    )
+                    flask.current_app.mailer.send(msg)
+
+    @classmethod
+    def inform_user(cls, account, form_data):
+        """
+        Send information about new account registration to the user.
+        Use user`s preferred locale for email content translations.
+        """
+        mail_locale = account.locale
+        if not mail_locale or mail_locale == 'None':
+            mail_locale = flask.current_app.config['BABEL_DEFAULT_LOCALE']
+        with force_locale(mail_locale):
+            msg = flask_mail.Message(
+                gettext(
+                    "[%(app_name)s] Account registration - %(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(
+                'registration/email_user.txt',
+                account = account,
+                justification = form_data['justification']
+            )
+            flask.current_app.mailer.send(msg)
+
+    def do_before_action(self, item):  # pylint: disable=locally-disabled,no-self-use,unused-argument
+        item.roles = [vial.const.ROLE_USER]
+        item.enabled = False
+
+    def do_after_action(self, item):  # pylint: disable=locally-disabled,no-self-use,unused-argument
+        self.inform_admins(item, self.response_context['form_data'])
+        self.inform_managers(item, self.response_context['form_data'])
+        self.inform_user(item, self.response_context['form_data'])
+
+
+
 class ItemUpdateView(ItemActionView):  # pylint: disable=locally-disabled,abstract-method
     """
     Base class for item *update* action views. These views update existing items
-- 
GitLab