Disclaimer
In the Q2 platform, a Disclaimer is an interim page the end user must view and accept before being routed to their accounts or landing page. Disclaimers are usually configured as their own extension, a special Form that is displayed immediately after login and must be accepted before access to the wider application is granted. Another key feature of Disclaimers is that they can be configured to only display if a user has not already seen and accepted them previously.
In the Q2 database, we support Disclaimers with a host of tables, including Q2_Disclaimer, Q2_DisclaimerBundle, Q2_DisclaimerBundleToGroup, Q2_DisclaimerElement, Q2_DisclaimerFormData, and Q2_DisclaimerFormAcceptance. Each of these are accessed during a Disclaimer workflow, but to make things easier we created the Disclaimer DbObject.
Note
For a more thorough explanation of DbObjects, refer to Database Objects
Here is an example Disclaimer extension which will prompt the user to read and accept its contents before the landing page is
accessed. To follow along, start by generating a new extension: q2 create_extension -t tecton_server Foo
from q2_sdk.core.http_handlers.tecton_server_handler import Q2TectonServerRequestHandler
from q2_sdk.hq.models.hq_commands import HqCommands
from q2_sdk.hq.db.disclaimer import Disclaimer
from .install.db_plan import DbPlan
class FooHandler(Q2TectonServerRequestHandler):
DB_PLAN = DbPlan()
FRIENDLY_NAME = 'Foo' # this will be used for end user facing references to this extension like Central and Menu items.
DEFAULT_MENU_ICON = 'landing-page' # this will be the default icon used if extension placed at top level (not used if a child element)
CONFIG_FILE_NAME = 'Foo' # configuration/Foo.py file must exist if REQUIRED_CONFIGURATIONS exist
TECTON_URL = 'https://cdn1.onlineaccess1.com/cdn/base/tecton/v1.38.0/q2-tecton-sdk.js'
@property
def router(self):
router = super().router
router.update(
{
'default': self.default,
'submit': self.submit,
}
)
return router
async def default(self):
template = self.get_template('index.html.jinja2', {})
html = self.get_tecton_form(
"Foo",
custom_template=template,
routing_key="submit"
)
return html
async def submit(self):
# Do not mark the disclaimer as accepted if the user did not check the box
if self.form_fields.get('accept') != 'on':
return ''
self.logger.info('Accepting disclaimer for user %s' % self.online_user.user_id)
disclaimer_obj = Disclaimer(self.logger, hq_credentials=self.hq_credentials)
await disclaimer_obj.accept_for_user_id(
self.online_user.user_id,
['Logon'] # This matches the name in Q2_Disclaimer (set by the DbPlan)
)
self.set_hq_commands(
HqCommands(
HqCommands.AccountReloadType.NO_RELOAD,
[],
force_success_response=True
)
)
return ''
This must be paired with the following file in ./templates/index.html.jinja2
<q2-section label='Foo'>
This is a legally binding disclaimer
<div class="q2-row">
<q2-checkbox name='accept' class="q2-col xs-6" type="toggle" alignment="left">I accept</q2-checkbox>
</div>
</q2-section>
<script src="{{ this.base_assets_url }}/index.js"></script>

As you can see, Disclaimers require a fair amount of interaction with HQ and the DB.
First, let’s start with the DB_PLAN reference– the database must be configured so that your Disclaimer displays at the appropriate
time in the user’s workflow. This database “plan” is defined in ./install/db_plan.py
:
from q2_sdk.core.install_steps import db_plan
class DbPlan(db_plan.DbPlan):
def __init__(self):
super().__init__()
self.ui_text_prefix = 'Foo' # Will be used for all self.ui_text_elements
self.disclaimers = [
db_plan.Disclaimer(
'Foo', # Display Name
'Foo', # Corresponding Form Shortname
disclaimer_type='Logon' # Logon has special meaning. Otherwise leave blank to use this disclaimer's ShortName
)
]
That db_plan.Disclaimer
instance will make the required database updates for us when we run
q2 install
for this extension. You can execute only the database plan with
q2 run_db_plan
, or reverse the database updates with q2 run_db_plan --reverse
.
Now that the database is updated, let’s examine the extension code. Just like any other
form, the user will first be routed to the default()
method. We’ve made
a simple form containing a short message and a checkbox to signify acceptance.
The real power of Disclaimers is in the submit()
method. First, we check to see whether the user
has checked the I accept box. If not, we return immediately, which will log the user
out of the application entirely.
If the user has accepted, we need to mark the form as accepted in the database and prompt HQ to refresh
disclaimer acceptance to allow the user to continue using the application. HQ has a method called DisclaimerFormMarkAsAccepted
,
which takes in a user_id
and a *disclaimer_form_id*
as input. This is NOT the same value as the form_id
referenced in
self.form_info.form_id
, so we must query it from the database. Luckily, the Caliper SDK has a built-in DbObject helper for this purpose,
so we can instantiate a Disclaimer instance and call get_disclaimer_form_data()
.
After the form is marked as accepted in the database, we can communicate with HQ and refresh disclaimer
acceptance by calling self.set_hq_commands
with an appropriately shaped instance of the HqCommands
object.
Note
Click here for more details on the set_hq_commands method.
Configuration Options
Other ways to configure Disclaimers include:
Configuring the Disclaimer to serve as a reminder, allowing the user to continue on to the main page even without Disclaimer acceptance
HqCommands
contains adisclaimer_id_to_skip
property which also refers to the value stored indisclaimer_form_id
above. Passing this value todisclaimer_id_to_skip
in theHqCommands
object will mark the Disclaimer as ‘ignored’ for the rest of the user’s session, and allow access to the application if the Disclaimer is not accepted.