예제 #1
0
def index():

    session.clear()

    w = WrapBokeh(PAGE_URL, app.logger)

    # Create a dominate document, see https://github.com/Knio/dominate
    # this line should go after any "return redirect" statements
    w.dominate_document()
    url_page_css(w.dom_doc, PAGE_URL)

    args, _redirect_page_metrics = w.process_req(request)
    if not args: return _redirect_page_metrics
    logger.info("{} : args {}".format(PAGE_URL, args))

    redir, url = index_menu_redirect(args)
    if redir: return redirect(url)

    if args.get("b_login", False): return redirect(COMMON_URL_LOGIN)

    doc_layout = layout(sizing_mode='scale_width')
    index_toolbar_menu(w, doc_layout, args)

    doc_layout.children.append(
        Div(text="""<h1>Index Page Welcome here...</h1>"""))

    w.init()

    return w.render(doc_layout)
예제 #2
0
def land():

    # TODO: This needs to be a decorator
    if not session.get('user_id', False): return redirect(COMMON_URL_LOGIN)
    user = User.get_by_id(session['user_id'])

    w = WrapBokeh(PAGE_URL, app.logger)
    w.init()

    # Create a dominate document, see https://github.com/Knio/dominate
    # this line should go after any "return redirect" statements
    w.dominate_document()
    url_page_css(w.dom_doc, PAGE_URL)

    args, _redirect_page_metrics = w.process_req(request)
    if not args: return _redirect_page_metrics
    app.logger.info("{} : args {}".format(PAGE_URL, args))

    redir, url = toolbar_menu_redirect(args)
    if redir: return redirect(url)

    doc_layout = layout(sizing_mode="fixed")
    page_toolbar_menu(w, doc_layout, args, user)

    doc_layout.children.append(
        Div(text="""<h1>Your Stuff Goes Here...</h1>"""))

    return w.render(doc_layout)
예제 #3
0
def init_widgets():
    global widgets

    widgets = WrapBokeh(PAGE_URL, app.logger)

    widgets.add(
        "sel_nexturl",
        Select(options=[('99', 'Select Next Page'), ('0', 'Home'),
                        ('2', 'Form Example'), ('3', 'Page C'),
                        ('4', 'Page D')],
               value=None,
               title="Select URL"))

    # Next
    # https://bokeh.pydata.org/en/latest/docs/user_guide/examples/interaction_data_table.html

    widgets.init()
예제 #4
0
def common__account_show():

    # TODO: This needs to be a decorator
    if not session.get('user_id', False): return redirect(COMMON_URL_LOGIN)
    user = User.get_by_id(session['user_id'])
    if user is None or not RolesUsers.user_has_role(user,
                                                    ["ADMIN", "ACCOUNT"]):
        # this should never happen... logout if it does...
        logger.error(
            "Unable to find user id {} or insufficient privileges".format(
                session['user_id']))
        session.pop('user_id', None)
        redirect(COMMON_URL_INDEX)

    users = User.get_username(None, True)
    data = {
        "username": [],
        "lname": [],
        "fname": [],
        "email": [],
        "roles": [],
        "_row_color": [],
    }
    for u in users:
        data["username"].append(u.username)
        data["lname"].append(u.lname)
        data["fname"].append(u.fname)
        data["email"].append(u.email)
        data["roles"].append(",".join([r.name for r in u.roles]))
        data["_row_color"].append(GUI.NOTICES_NORMAL)

    template = """<div style="background:<%=_row_color%>"; color="white";><%= value %></div>"""
    formatter = HTMLTemplateFormatter(template=template)
    hidden_columns = ["_row_color"]
    widths = {
        "username": 100,
        "lname": 100,
        "fname": 100,
        "email": 200,
        "roles": 380,
    }

    source = ColumnDataSource(data)

    columns = []
    for c in source.data.keys():
        if c not in hidden_columns:
            w = widths[c]
            columns.append(
                TableColumn(field=c, title=c, formatter=formatter, width=w))

    data_table = DataTable(source=source,
                           columns=columns,
                           width=900,
                           height=280,
                           fit_columns=False,
                           index_position=None)

    w = WrapBokeh(PAGE_URL, logger)

    w.init()

    # Create a dominate document, see https://github.com/Knio/dominate
    # this line should go after any "return redirect" statements
    w.dominate_document()
    url_page_css(w.dom_doc, PAGE_URL)

    args, _redirect_page_metrics = w.process_req(request)
    #if not args: return _redirect_page_metrics
    logger.info("{} : args {}".format(PAGE_URL, args))

    redir, url = toolbar_menu_redirect(args)
    if redir: return redirect(url)

    doc_layout = layout(sizing_mode='scale_width')
    page_toolbar_menu(w, doc_layout, args, user)

    doc_layout.children.append(data_table)

    return w.render(doc_layout)
예제 #5
0
def init_widgets():
    global widgets

    widgets = WrapBokeh(PAGE_URL, app.logger)
    widgets.add(
        "s_age",
        Slider(title='Age',
               value=25,
               start=1,
               end=99,
               step=1,
               callback_policy='mouseup',
               width=200,
               css_classes=['s_age']))

    widgets.add(
        "dp_birthday",
        DatePicker(title="Birthday",
                   min_date=None,
                   max_date=datetime.today(),
                   value=datetime.today(),
                   width=300,
                   css_classes=['dp_birthday']))

    widgets.add(
        "msel_fruit",
        MultiSelect(options=[('Apples', 'Apples'),
                             ('Strawberries', 'Strawberries'),
                             ('Oranges', 'Oranges'),
                             ('Grapefruit', 'Grapefruit'),
                             ('Banannas', 'Banannas')],
                    value=[],
                    title="Fruit",
                    css_classes=['msel_fruit']))

    widgets.add(
        "ds_birthday",
        DateSlider(title="Birthday",
                   end=datetime.today(),
                   start=datetime.today() - timedelta(days=30),
                   step=1,
                   value=datetime.today(),
                   callback_policy='mouseup',
                   css_classes=['ds_birthday']))

    widgets.add(
        "s_amp",
        Slider(title='Amplitude',
               value=1,
               start=0,
               end=2,
               step=0.1,
               callback_policy='mouseup',
               width=200,
               css_classes=['s_amp']))

    widgets.add("b_test", Button(label="Press me!", css_classes=['b_test']))
    widgets.add("toggle_1", Toggle(label="Toggle me!",
                                   css_classes=['toggle_1']))
    widgets.add(
        "dropdn_1",
        Dropdown(label="Menu",
                 menu=[("First", '0'), ("Second", '1'), None, ("End", '2')],
                 css_classes=['dropdn_1']))

    widgets.add(
        "sel_nexturl",
        Select(options=[('99', 'Select Next Page'),
                        ('1', 'Ajax Stream Example'), ('2', 'Form Example'),
                        ('3', 'Page C'), ('4', 'Page D')],
               value=None,
               title="Select URL",
               css_classes=['sel_nexturl']))

    widgets.add(
        "cbbg_music",
        CheckboxButtonGroup(labels=["Rock0", "Country0", "Classical0"],
                            active=[],
                            css_classes=['cbbg_music']))
    widgets.add(
        "cbg_music",
        CheckboxGroup(labels=["Rock1", "Country1", "Classical1"],
                      active=[],
                      css_classes=['cbg_music']))
    widgets.add(
        "rbg_music",
        RadioButtonGroup(labels=["Rock2", "Country2", "Classical2"],
                         active=None,
                         css_classes=['rbg_music']))
    widgets.add(
        "rg_music",
        RadioGroup(labels=["Rock3", "Country3", "Classical3"],
                   active=None,
                   css_classes=['rg_music']))

    widgets.add(
        "rslider_amp",
        RangeSlider(title='Amplitude',
                    value=(0.5, 1.5),
                    start=0,
                    end=2,
                    step=0.1,
                    callback_policy='mouseup',
                    width=200,
                    css_classes=['rslider_amp']))

    widgets.init()
예제 #6
0
def hello():
    global layout_done

    # create pywrapbokeh object
    widgets = WrapBokeh("/", app.logger)

    # see https://github.com/Knio/dominate
    widgets.dominate_document()  # create dominate document
    with widgets.dom_doc.body:  # add css elements here...
        style(raw("""body {background-color:powderblue;}"""))

    # check/get page metrics and get args for the page
    args, _redirect_page_metrics = widgets.process_req(request)
    if not args: return _redirect_page_metrics
    app.logger.info("{} : args {}".format("/", args))

    selected_fruit = args.get("sel_fruit", "Unknown")
    if selected_fruit is not "Unknown":
        selected_fruit = fruits[int(selected_fruit)][1]  # get name of fruit

    # check args here for actions that would redirect to another page
    # if args.get( name ) == something: return redirect( new_url )

    # add bokeh widgets to the pywrapbokeh object
    # - order does not matter
    # - API pattern,
    #   - WrapBokeh.add( <name_of_widget>, <bokeh API>(..., [css_classes=[<name_of_widget>]]))
    #   - if you want to affect widget properties (color, font size, etc) you need to set css_classes
    widgets.add(
        "sel_fruit",
        Select(options=fruits,
               value=None,
               title="Select Fruit",
               css_classes=['sel_fruit']))
    widgets.add_css("sel_fruit", {'select': {'background-color': "#6495ED"}})

    widgets.init()

    # start a bokeh layout
    doc_layout = widgets.layout()

    # append items to the document
    doc_layout.children.append(
        column(
            Div(text="""<h1>Hello World!</h1>"""),
            Paragraph(
                text="""Your favorite fruit is {}""".format(selected_fruit))))

    # append a pywrapbokeh widget
    doc_layout.children.append(widgets.get("sel_fruit"))

    # render page
    return widgets.render(doc_layout)
예제 #7
0
def login_recover():

    w = WrapBokeh(PAGE_URL, logger)

    w.add(
        "tin_uname_only",
        TextInput(title="User Name:",
                  placeholder="",
                  css_classes=['tin_uname_only']))
    w.add(
        "tin_email_only",
        TextInput(title="Email:",
                  placeholder="",
                  css_classes=['tin_email_only']))
    w.add("b_submit", Button(label="Submit", css_classes=['b_submit']))
    w.add("b_ok", Button(label="Ok", css_classes=['b_ok']))

    w.init()

    # Create a dominate document, see https://github.com/Knio/dominate
    # this line should go after any "return redirect" statements
    w.dominate_document()
    url_page_css(w.dom_doc, PAGE_URL)

    args, _redirect_page_metrics = w.process_req(request)
    if not args: return _redirect_page_metrics
    logger.info("{} : args {}".format(PAGE_URL, args))
    left_margin = int(int(args.get("windowWidth", 800)) * 0.2)

    if args.get("b_ok", False): return redirect(COMMON_URL_INDEX)

    redir, url = index_menu_redirect(args)
    if redir: return redirect(url)

    failed_credentials_match = False
    recovery_email_sent = False
    error_fields = {}
    submitted = args.get("b_submit", False)
    if submitted:
        validated, error_fields = User.validate(args)
        if validated:
            # uname, email format is valid, now lets see if it exists
            user = User.get_username(args.get("tin_uname_only"))
            if user in [None, []] or user.email != args.get("tin_email_only"):
                logger.error(
                    "Invalid username/pw ({}/{}) for login recovery".format(
                        user.username, args.get("tin_email_only")))
                failed_credentials_match = True

            if not failed_credentials_match:
                logger.info("user validated, sending recovery email")
                temp_pw = "12345"  # FIXME: make a random password

                # TODO: send email

                User.update(original_username=user.username,
                            first=user.fname,
                            last=user.lname,
                            username=user.username,
                            password=temp_pw,
                            email=user.email)
                recovery_email_sent = True

    doc_layout = layout(sizing_mode='scale_width')
    index_toolbar_menu(w, doc_layout, args)

    doc_layout = layout(sizing_mode='scale_width')

    # show error fields... if any
    if submitted and not validated:
        for key, value in error_fields.items():
            error = User.get_form_error_handle_from_err(
                key, value[0])  # process first error only
            w.add_css(key, error["css"])
            w.get(key).title = error.get('msg', "!NO MSG!")

    if recovery_email_sent:
        doc_layout.children.append(
            row([
                Spacer(width=left_margin),
                Div(text="""<h1>An Email has been sent!</h1>""")
            ]))
        doc_layout.children.append(
            row([Spacer(width=left_margin),
                 w.get("b_ok")]))

    elif failed_credentials_match:
        doc_layout.children.append(
            row([
                Spacer(width=left_margin),
                Div(text=
                    """<h1>Those credentials did not match a known user.</h1>""",
                    height=150)
            ]))
        doc_layout.children.append(
            row([Spacer(width=left_margin),
                 w.get("b_ok")]))

    else:
        w.add_css("tin_uname_only", {'input': {'width': '90%'}})
        w.add_css("tin_email_only", {'input': {'width': '90%'}})

        wbox = widgetbox(w.get("tin_uname_only"), w.get("tin_email_only"),
                         w.get("b_submit"))
        doc_layout.children.append(row([Spacer(width=left_margin), wbox]))

    return w.render(doc_layout)
예제 #8
0
def common__login():

    w = WrapBokeh(PAGE_URL, logger)

    w.add(
        "tin_uname",
        TextInput(title="Login Name:",
                  placeholder="",
                  css_classes=['tin_lname']))
    w.add(
        "tin_lpw",
        PasswordInput(title="Password:"******"",
                      css_classes=['tin_lpw']))
    w.add("b_submit", Button(label="Submit", css_classes=['b_submit']))
    w.add("b_signup", Button(label="Sign Up", css_classes=['b_signup']))
    w.add("b_recover",
          Button(label="Recover Password", css_classes=['b_recover']))

    w.init()

    # Create a dominate document, see https://github.com/Knio/dominate
    # this line should go after any "return redirect" statements
    w.dominate_document(title=session["title"])
    url_page_css(w.dom_doc, PAGE_URL)

    args, _redirect_page_metrics = w.process_req(request)
    if not args: return _redirect_page_metrics
    logger.info("{} : args {}".format(PAGE_URL, args))
    left_margin = int(int(args.get("windowWidth", 800)) * 0.1)

    redir, url = index_menu_redirect(args)
    if redir: return redirect(url)

    if args.get("b_signup", False): return redirect(COMMON_URL_LOGIN_SIGNUP)
    if args.get("b_recover", False): return redirect(COMMON_URL_LOGIN_RECOVER)

    login_failed = False
    if args.get("b_submit", False):
        uname = args.get("tin_uname", None)
        pw = w.get("tin_lpw").value

        if uname is not None and pw is not None:
            user = User.login(uname, pw)
            if user is not None:
                logger.info("{} {}".format(user.username, user.id))
                session['user_id'] = user.id
                login(user.id)
                return redirect(COMMON_URL_LAND)
            else:
                logger.info("Login failed for {}".format(uname))
                login_failed = True

    doc_layout = layout(sizing_mode='scale_width')
    index_toolbar_menu(w, doc_layout, args)

    if login_failed:
        doc_layout.children.append(
            row(
                Spacer(width=left_margin),
                column([
                    Div(text="""<p>Login failed, Recover Password?</p>"""),
                    w.get("b_recover")
                ])))

    w.add_css("b_submit",
              {'button': {
                  'background-color': '#98FB98',
                  'min-width': '60px'
              }})
    w.add_css("b_signup",
              {'button': {
                  'background-color': '#98FB98',
                  'min-width': '60px'
              }})
    w.add_css("tin_uname", {'input': {'width': '90%'}})
    w.add_css("tin_lpw", {'input': {'width': '90%'}})

    if app.config["app"]["user"]["signup_enabled"]:
        wbox = widgetbox(w.get("tin_uname"), w.get("tin_lpw"),
                         w.get("b_submit"), w.get("b_signup"))
    else:
        wbox = widgetbox(w.get("tin_uname"), w.get("tin_lpw"),
                         w.get("b_submit"))

    doc_layout.children.append(row([Spacer(width=left_margin), wbox]))

    return w.render(doc_layout)
예제 #9
0
def roles_edit():
    logger = logging.getLogger("TMI.roles_edit")

    def pop_session():
        session.pop("roles_initial", None)
        session.pop("roles", None)

    # TODO: This needs to be a decorator
    if not session.get('user_id', False): return redirect(COMMON_URL_LOGIN)
    user = User.get_by_id(session['user_id'])
    if user is None or not RolesUsers.user_has_role(user,
                                                    ["EDIT-ROLE", "ADD-USER"]):
        # this should never happen... logout if it does...
        logger.error("Unable to find user id {}".format(session['user_id']))
        session.pop('user_id', None)
        redirect(COMMON_URL_INDEX)

    w = WrapBokeh(PAGE_URL, logger)

    w.add(
        "sel_uname",
        Select(options=[],
               value=None,
               title="Select User",
               css_classes=['sel_uname']))
    w.add("cbg_roles",
          CheckboxGroup(labels=[], active=[], css_classes=['cbg_roles']))
    w.add("b_submit", Button(label="Update", css_classes=['b_submit']))
    w.add("b_cancel", Button(label="Cancel", css_classes=['b_cancel']))

    w.init()

    user = User.get_by_id(session['user_id'])
    if user is None:
        logger.error("Unable to find user id {}".format(session['user_id']))
        session.pop('user_id')
        redirect(COMMON_URL_INDEX)

    # Create a dominate document, see https://github.com/Knio/dominate
    # this line should go after any "return redirect" statements
    w.dominate_document()
    url_page_css(w.dom_doc, PAGE_URL)

    args, _redirect_page_metrics = w.process_req(request)
    if not args: return _redirect_page_metrics
    logger.info("{} : args {}".format(PAGE_URL, args))

    redir, url = toolbar_menu_redirect(args)
    if redir:
        pop_session()
        return redirect(url)

    if args.get("b_cancel", False):
        pop_session()
        return redirect(COMMON_URL_LAND)

    updated = False
    if args.get(
            "b_submit",
            False) and session["roles_initial"] != w.get("cbg_roles").active:
        # need to convert the CheckboxGroup list indexes to Role IDs
        selected_idexes = w.get("cbg_roles").active
        if selected_idexes == [None]: selected_idexes = []
        selected_roles = []
        for idx in selected_idexes:
            selected_roles.append(session["roles"][idx])

        edit_user = User.get_username(w.get("sel_uname").value)
        logger.info("{} updated roles {}".format(edit_user.username,
                                                 selected_roles))
        success = User.update_roles(edit_user.username, selected_roles)
        if success: updated = True

    doc_layout = layout(sizing_mode="fixed")
    page_toolbar_menu(w, doc_layout, args, user)

    # populate users
    all_users = User.get_username(None, all=True)
    sel_users = [("Select", "Select User")]
    for u in all_users:
        if u.username in app.config["app"]["user"]["protected"]: continue
        sel_users.append((u.username, u.username))
    w.get("sel_uname").options = sel_users
    w.get("sel_uname").value = args.get("sel_uname",
                                        None)  # last value or none
    session["roles_initial"] = []

    # new selection was done, update the roles
    if w.get("sel_uname").value not in ['Select', None]:
        edit_user = User.get_username(w.get("sel_uname").value)
        roles = []
        user_ids = []
        session["roles"] = []
        for _id, _name, _desc in Role.get_all():
            session["roles"].append(_name)
            roles.append(_desc)
            if RolesUsers.user_has_role(edit_user, _name):
                user_ids.append(roles.index(_desc))
        w.get("cbg_roles").labels = roles

        w.get("cbg_roles").active = user_ids

        if args['callerWidget'] == 'sel_uname' or updated:
            session["roles_initial"] = w.get("cbg_roles").active

    if args["callerWidget"] == 'cbg_roles':
        if len(args['cbg_roles']) == 0: w.get("cbg_roles").active = []
        else:
            w.get("cbg_roles").active = [
                int(i) for i in args['cbg_roles'].split(",")
            ]

    # change submit button if there is a change in roles
    if session["roles_initial"] == w.get("cbg_roles").active:
        w.add_css(
            "b_submit", {
                'button': {
                    'background-color': GUI.BUTTON_DISABLED_GRAY,
                    'pointer-events': None
                }
            })
    else:
        w.add_css("b_submit",
                  {'button': {
                      'background-color': GUI.BUTTON_ENABLED_GREEN
                  }})

    w.add_css("b_cancel", {'button': {'background-color': GUI.BUTTON_CANCEL}})

    wbox = widgetbox(w.get("sel_uname"), w.get("cbg_roles"), w.get("b_submit"),
                     w.get("b_cancel"))
    left_margin = int(int(args.get("windowWidth", 800)) * 0.2)
    doc_layout.children.append(row([Spacer(width=left_margin), wbox]))

    return w.render(doc_layout)
예제 #10
0
def init_widgets():
    global widgets
    widgets = WrapBokeh(PAGE_URL, app.logger)

    widgets.add(
        "tin_fname",
        TextInput(title="First Name:",
                  placeholder="first name",
                  css_classes=['tin_fname']))
    widgets.add(
        "tin_lname",
        TextInput(title="Last Name:",
                  placeholder="last name",
                  css_classes=['tin_lname']))
    widgets.add("b_submit", Button(label="Submit", css_classes=['b_submit']))

    countries = [('', 'Select Country')] + [(x, x) for x in geo_info.keys()]
    states = [('', 'Select State')] + [(x, x)
                                       for x in geo_info["United States"]]

    widgets.add(
        "sel_country",
        Select(options=countries,
               value=None,
               title="Select Country",
               css_classes=['sel_country']))
    widgets.add(
        "sel_state",
        Select(options=states,
               value=None,
               title="Select State",
               css_classes=['sel_state']))

    widgets.add(
        "sel_nexturl",
        Select(options=[('99', 'Select Next Page'), ('0', 'Home'),
                        ('1', 'Ajax Stream Example'), ('3', 'Page C'),
                        ('4', 'Page D')],
               value=None,
               title="Select URL",
               css_classes=['sel_nexturl']))

    widgets.init()
def login_signup():

    w = WrapBokeh(PAGE_URL, logger)

    w.add(
        "tin_fname",
        TextInput(title="First Name:",
                  placeholder="",
                  css_classes=['tin_fname']))
    w.add(
        "tin_lname",
        TextInput(title="Last Name:",
                  placeholder="",
                  css_classes=['tin_lname']))
    w.add(
        "tin_uname",
        TextInput(title="User Name:",
                  placeholder="",
                  css_classes=['tin_uname']))
    w.add(
        "tin_lpw",
        PasswordInput(title="Password:"******"",
                      css_classes=['tin_lpw']))
    w.add(
        "tin_lpw_confirm",
        PasswordInput(title="Confirm Password:"******"",
                      css_classes=['tin_lpw_confirm']))
    w.add("tin_email",
          TextInput(title="Email:", placeholder="", css_classes=['tin_email']))
    w.add("b_submit", Button(label="Submit", css_classes=['b_submit']))

    w.init()

    # Create a dominate document, see https://github.com/Knio/dominate
    # this line should go after any "return redirect" statements
    w.dominate_document()
    url_page_css(w.dom_doc, PAGE_URL)

    args, _redirect_page_metrics = w.process_req(request)
    if not args: return _redirect_page_metrics
    logger.info("{} : args {}".format(PAGE_URL, args))

    redir, url = index_menu_redirect(args)
    if redir: return redirect(url)

    error_fields = {}
    submitted = args.get("b_submit", False)
    if submitted:
        validated, error_fields = User.validate(args)

    # on submit, validate form contents, show errors...
    if submitted and validated:
        logger.info("validated: {}".format(args))
        User.add(first=args["tin_fname"],
                 last=args["tin_lname"],
                 username=args["tin_uname"],
                 password=args["tin_lpw"],
                 email=args["tin_email"])
        return redirect(COMMON_URL_INDEX)

    doc_layout = layout(sizing_mode='scale_width')
    index_toolbar_menu(w, doc_layout, args)

    # show error fields... if any
    if submitted and not validated:
        for key, value in error_fields.items():
            error = User.get_form_error_handle_from_err(
                key, value[0])  # process first error only
            w.add_css(key, error["css"])
            w.get(key).title = error.get('msg', "!NO MSG!")

    w.add_css("tin_fname", {'input': {'width': '90%'}})
    w.add_css("tin_lname", {'input': {'width': '90%'}})
    w.add_css("tin_uname", {'input': {'width': '90%'}})
    w.add_css("tin_lpw", {'input': {'width': '90%'}})
    w.add_css("tin_lpw_confirm", {'input': {'width': '90%'}})
    w.add_css("tin_email", {'input': {'width': '90%'}})

    wbox = widgetbox(w.get("tin_fname"), w.get("tin_lname"),
                     w.get("tin_uname"), w.get("tin_lpw"),
                     w.get("tin_lpw_confirm"), w.get("tin_email"),
                     w.get("b_submit"))
    left_margin = int(int(args.get("windowWidth", 800)) * 0.2)
    doc_layout.children.append(row([Spacer(width=left_margin), wbox]))

    return w.render(doc_layout)
예제 #12
0
def common__account_add():

    # TODO: This needs to be a decorator
    if not session.get('user_id', False): return redirect(COMMON_URL_LOGIN)
    user = User.get_by_id(session['user_id'])
    if user is None or not RolesUsers.user_has_role(user,
                                                    ["ADMIN", "ADD-USER"]):
        # this should never happen... logout if it does...
        logger.error("Unable to find user id {}".format(session['user_id']))
        session.pop('user_id', None)
        redirect(COMMON_URL_INDEX)

    w = WrapBokeh(PAGE_URL, logger)

    w.add(
        "tin_fname",
        TextInput(title="First Name:",
                  placeholder="",
                  css_classes=['tin_fname']))
    w.add(
        "tin_lname",
        TextInput(title="Last Name:",
                  placeholder="",
                  css_classes=['tin_lname']))
    w.add(
        "tin_uname",
        TextInput(title="User Name:",
                  placeholder="",
                  css_classes=['tin_uname']))
    w.add(
        "tin_lpw",
        PasswordInput(title="Password:"******"",
                      css_classes=['tin_lpw']))
    w.add(
        "tin_lpw_confirm",
        PasswordInput(title="Confirm Password:"******"",
                      css_classes=['tin_lpw_confirm']))
    w.add("tin_email",
          TextInput(title="Email:", placeholder="", css_classes=['tin_email']))
    w.add("b_submit", Button(label="Submit", css_classes=['b_submit']))

    w.init()

    # Create a dominate document, see https://github.com/Knio/dominate
    # this line should go after any "return redirect" statements
    w.dominate_document()
    url_page_css(w.dom_doc, PAGE_URL)

    args, _redirect_page_metrics = w.process_req(request)
    if not args: return _redirect_page_metrics
    logger.info("{} : args {}".format(PAGE_URL, args))

    redir, url = toolbar_menu_redirect(args)
    if redir: return redirect(url)

    error_fields = {}
    submitted = args.get("b_submit", False)
    if submitted:
        validated, error_fields = User.validate(args)

    # on submit, validate form contents, show errors...
    if submitted and validated:
        logger.info("validated: {}".format(args))
        User.add(first=args["tin_fname"],
                 last=args["tin_lname"],
                 username=args["tin_uname"],
                 password=args["tin_lpw"],
                 email=args["tin_email"])
        return redirect(COMMON_URL_ACCOUNT_ADD)

    doc_layout = layout(sizing_mode='scale_width')
    page_toolbar_menu(w, doc_layout, args, user)

    # show error fields... if any
    if submitted and not validated:
        for key, value in error_fields.items():
            error = User.get_form_error_handle_from_err(
                key, value[0])  # process first error only
            w.add_css(key, error["css"])
            w.get(key).title = error.get('msg', "!NO MSG!")

    w.add_css("tin_fname", {'input': {'width': '90%'}})
    w.add_css("tin_lname", {'input': {'width': '90%'}})
    w.add_css("tin_uname", {'input': {'width': '90%'}})
    w.add_css("tin_lpw", {'input': {'width': '90%'}})
    w.add_css("tin_lpw_confirm", {'input': {'width': '90%'}})
    w.add_css("tin_email", {'input': {'width': '90%'}})

    wbox = widgetbox(w.get("tin_fname"), w.get("tin_lname"),
                     w.get("tin_uname"), w.get("tin_lpw"),
                     w.get("tin_lpw_confirm"), w.get("tin_email"),
                     w.get("b_submit"))
    left_margin = int(int(args.get("windowWidth", 800)) * 0.2)
    doc_layout.children.append(row([Spacer(width=left_margin), wbox]))

    return w.render(doc_layout)