Re-use: Allowing users to log in to your system

There’s no need to build login functionality yourself, Reahl provides this already.

You can opt to use only the provided model and write your own user interface, or use both the model and the user interface shipped by Reahl.

Re-using only a domain model

The user and system account functionality is in the reahl-domain component, in the module reahl.domain.systemaccountmodel.

To be able to use it in your code, list the reahl-domain component as a dependency in the .reahlproject file of your own project:

<project type="egg">
  <deps purpose="run">
    <egg name="reahl-web"/>
    <egg name="reahl-component"/>
    <egg name="reahl-sqlalchemysupport"/>
    <egg name="reahl-web-declarative"/>
    <egg name="reahl-domain"/>
  </deps>

  <deps purpose="build">
    <thirdpartyegg name="pytest-runner"/>
  </deps>

  <deps purpose="test">
    <thirdpartyegg name="pytest" minversion="3.0" maxversion="3.9999"/>
    <egg name="reahl-tofu"/>
    <egg name="reahl-stubble"/>
    <egg name="reahl-dev"/>
    <egg name="reahl-webdev"/>
  </deps>


</project>

Note

Remember, each time you change a .reahlproject file, you need to run reahl setup -- develop -N, as explained in Fetch Addresses from the database.

Use AccountManagementInterface in your code to find the current LoginSession.

An AccountManagementInterface has many Fields and Events available for use by user interface code. It allows for logging in and out, but also for registration, verification of the email addresses of new registrations, resetting forgotten passwords, etc.




from __future__ import print_function, unicode_literals, absolute_import, division

from reahl.web.fw import UserInterface
from reahl.web.layout import PageLayout
from reahl.web.bootstrap.ui import HTML5Page, P, Alert
from reahl.web.bootstrap.forms import Form, TextInput, Button, FormLayout, ButtonLayout, PasswordInput
from reahl.web.bootstrap.navs import Nav, TabLayout
from reahl.web.bootstrap.grid import ColumnLayout, ColumnOptions, ResponsiveSize, Container
from reahl.domain.systemaccountmodel import AccountManagementInterface, LoginSession



class MenuPage(HTML5Page):
    def __init__(self, view, main_bookmarks):
        super(MenuPage, self).__init__(view)
        self.use_layout(PageLayout(document_layout=Container()))
        contents_layout = ColumnLayout(ColumnOptions('main', size=ResponsiveSize(md=4))).with_slots()
        self.layout.contents.use_layout(contents_layout)
        self.layout.header.add_child(Nav(view).use_layout(TabLayout()).with_bookmarks(main_bookmarks))


class LoginForm(Form):
    def __init__(self, view):
        super(LoginForm, self).__init__(view, 'login')
        self.use_layout(FormLayout())
        accounts = AccountManagementInterface.for_current_session()

        if self.exception:
            self.add_child(Alert(view, self.exception.as_user_message(), 'warning'))

        self.layout.add_input(TextInput(self, accounts.fields.email))
        self.layout.add_input(PasswordInput(self, accounts.fields.password))

        self.define_event_handler(accounts.events.login_event)
        btn = self.add_child(Button(self, accounts.events.login_event))
        btn.use_layout(ButtonLayout(style='primary'))


class LoginUI(UserInterface):
    def assemble(self):
        login_session = LoginSession.for_current_session()
        if login_session.account:
            logged_in_as = login_session.account.email
        else:
            logged_in_as = 'Guest'

        home = self.define_view('/', title='Home')
        home.set_slot('main', P.factory(text='Welcome %s' % logged_in_as))

        login_page = self.define_view('/login', title='Log in')
        login_page.set_slot('main', LoginForm.factory())
        
        bookmarks = [i.as_bookmark(self) for i in [home, login_page]]
        self.define_page(MenuPage, bookmarks)






Exercising tutorial.login1bootstrap

When you fire up the example application, there will be no users registered to play with. To create some, run:

reahl demosetup

This creates a user and password using the password and email address in:

@uses(web_fixture=WebFixture)
class LoginFixture(Fixture):

    def new_browser(self):
        return Browser(self.web_fixture.new_wsgi_app(site_root=LoginUI))

    password = 'topsecret'

    def new_account(self):
        account = EmailAndPasswordSystemAccount(email='[email protected]')
        Session.add(account)
        account.set_new_password(account.email, self.password)
        account.activate()
        return account

Re-using the user interface as well

If you want to use the provided user interface as well, you can graft an AccountUI onto its own new URL underneath LoginUI. The URLs of the Views of AccountUI are then appended to that URL.

Call define_user_interface() to graft AccountUI onto LoginUI.

In this call you need to specify which Slot name as declared by AccountUI plugs into which Slot names we have available on MenuPage.

AccountUI expects a number of Bookmarks to UrlBoundViews that contain legal text specific to your site. These Bookmarks are sent as an additional keyword argument to define_user_interface(), from where it will be delivered to AccountUI.




from __future__ import print_function, unicode_literals, absolute_import, division
from reahl.web.fw import UserInterface
from reahl.web.layout import PageLayout
from reahl.web.bootstrap.ui import HTML5Page, P
from reahl.web.bootstrap.navs import Nav, TabLayout
from reahl.web.bootstrap.grid import ColumnLayout, ColumnOptions, ResponsiveSize, Container
from reahl.domain.systemaccountmodel import LoginSession
from reahl.domainui.bootstrap.accounts import AccountUI



class MenuPage(HTML5Page):
    def __init__(self, view, main_bookmarks):
        super(MenuPage, self).__init__(view)
        self.use_layout(PageLayout(document_layout=Container()))
        contents_layout = ColumnLayout(ColumnOptions('main', size=ResponsiveSize(md=4))).with_slots()
        self.layout.contents.use_layout(contents_layout)
        self.layout.header.add_child(Nav(view).use_layout(TabLayout()).with_bookmarks(main_bookmarks))


class LegalNotice(P):
    def __init__(self, view, text, name):
        super(LegalNotice, self).__init__(view, text=text, css_id=name)
        self.set_as_security_sensitive()


class LoginUI(UserInterface):
    def assemble(self):
        login_session = LoginSession.for_current_session()
        if login_session.account:
            logged_in_as = login_session.account.email
        else:
            logged_in_as = 'Guest'

        home = self.define_view('/', title='Home')
        home.set_slot('main', P.factory(text='Welcome %s' % logged_in_as))

        terms_of_service = self.define_view('/terms_of_service', title='Terms of service')
        terms_of_service.set_slot('main', LegalNotice.factory('The terms of services defined as ...', 'terms'))

        privacy_policy = self.define_view('/privacy_policy', title='Privacy policy')
        privacy_policy.set_slot('main', LegalNotice.factory('You have the right to remain silent ...', 'privacypolicy'))

        disclaimer = self.define_view('/disclaimer', title='Disclaimer')
        disclaimer.set_slot('main', LegalNotice.factory('Disclaim ourselves from negligence ...', 'disclaimer'))

        class LegalBookmarks(object):
            terms_bookmark = terms_of_service.as_bookmark(self)
            privacy_bookmark = privacy_policy.as_bookmark(self)
            disclaimer_bookmark = disclaimer.as_bookmark(self)

        accounts = self.define_user_interface('/accounts', AccountUI,
                                      {'main_slot': 'main'},
                                      name='accounts', bookmarks=LegalBookmarks)

        account_bookmarks = [accounts.get_bookmark(relative_path=relative_path) 
                             for relative_path in ['/login', '/register']]
        bookmarks = [home.as_bookmark(self)]+account_bookmarks
        self.define_page(MenuPage, bookmarks)