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 pyproject.toml file of your own project:
[build-system]
requires = [
"setuptools >= 68",
"wheel",
"setuptools-git >= 1.1",
"pytest-runner",
"toml",
"reahl-component-metadata >= 7.0.0"
]
build-backend = "setuptools.build_meta"
[project]
name = "login1bootstrap"
version = "0.1"
requires-python = ">=3.8"
dependencies = [
"reahl-web>=7.0,<7.1",
"reahl-component>=7.0,<7.1",
"reahl-sqlitesupport>=7.0,<7.1",
"reahl-sqlalchemysupport>=7.0,<7.1",
"reahl-web-declarative>=7.0,<7.1",
"reahl-domain>=7.0,<7.1"
]
authors = [
{name = "Iwan Vosloo", email = "[email protected]"},
{name = "Craig Sparks", email = "[email protected]"}
]
[tool.setuptools]
py-modules = ["login1bootstrap"]
[tool.setuptools.packages.find]
exclude = ["etc", "build", "dist"]
[tool.reahl-component]
Note
Remember, each time you change a pyproject.toml file, as explained in Fetch Addresses from the database, you need to
run: python -m pip install --no-deps -e .
Use for_current_session()
in your code to find the current LoginSession
.
An AccountManagementInterface
has
many Field
s and
Event
s 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 reahl.web.fw import UserInterface
from reahl.web.layout import PageLayout
from reahl.web.bootstrap.page import HTML5Page
from reahl.web.bootstrap.ui import P
from reahl.web.bootstrap.forms import Form, TextInput, Button, FormLayout, 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().__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().__init__(view, 'login')
self.use_layout(FormLayout())
accounts = AccountManagementInterface.for_current_session()
if self.exception:
self.layout.add_alert_for_domain_exception(self.exception)
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)
self.add_child(Button(self, accounts.events.login_event, 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:
pytest -o python_functions=demo_setup
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 View
s 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 Bookmark
s to UrlBoundView
s that
contain legal text specific to your site. These Bookmark
s are sent
as an additional keyword argument to
define_user_interface()
, from where
it will be delivered to AccountUI
.
from reahl.web.fw import UserInterface
from reahl.web.layout import PageLayout
from reahl.web.bootstrap.page import HTML5Page
from reahl.web.bootstrap.ui import 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().__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().__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:
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', '/registerHelp', '/verify']]
bookmarks = [home.as_bookmark(self)]+account_bookmarks
self.define_page(MenuPage, bookmarks)