summaryrefslogtreecommitdiff
path: root/addons/portal/views
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/portal/views
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/portal/views')
-rw-r--r--addons/portal/views/assets.xml33
-rw-r--r--addons/portal/views/portal_templates.xml584
2 files changed, 617 insertions, 0 deletions
diff --git a/addons/portal/views/assets.xml b/addons/portal/views/assets.xml
new file mode 100644
index 00000000..2fbcf61d
--- /dev/null
+++ b/addons/portal/views/assets.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <template id="_assets_primary_variables" inherit_id="web._assets_primary_variables">
+ <xpath expr="//link[last()]" position="after">
+ <link rel="stylesheet" type="text/scss" href="/portal/static/src/scss/primary_variables.scss"/>
+ </xpath>
+ </template>
+ <template id="_assets_frontend_helpers" inherit_id="web_editor._assets_frontend_helpers">
+ <xpath expr="//link" position="before">
+ <link rel="stylesheet" type="text/scss" href="/portal/static/src/scss/bootstrap_overridden.scss"/>
+ </xpath>
+ </template>
+
+ <template id="assets_frontend" inherit_id="web_editor.assets_frontend" name="Portal Assets" priority="15">
+ <xpath expr="//link[last()]" position="after">
+ <link rel="stylesheet" type="text/scss" href="/portal/static/src/scss/bootstrap.extend.scss"/>
+ <link rel="stylesheet" type="text/scss" href="/portal/static/src/scss/portal.scss"/>
+ </xpath>
+ <xpath expr="//script[last()]" position="after">
+ <script type="text/javascript" src="/portal/static/src/js/portal.js"></script>
+ <script type="text/javascript" src="/portal/static/src/js/portal_chatter.js"></script>
+ <script type="text/javascript" src="/portal/static/src/js/portal_composer.js"></script>
+ <script type="text/javascript" src="/portal/static/src/js/portal_signature.js"></script>
+ <script type="text/javascript" src="/portal/static/src/js/portal_sidebar.js"></script>
+ </xpath>
+ </template>
+
+ <template id="assets_tests" name="Portal Assets Tests" inherit_id="web.assets_tests">
+ <xpath expr="." position="inside">
+ <script type="text/javascript" src="/portal/static/tests/tours/portal.js"></script>
+ </xpath>
+ </template>
+</odoo>
diff --git a/addons/portal/views/portal_templates.xml b/addons/portal/views/portal_templates.xml
new file mode 100644
index 00000000..371391c2
--- /dev/null
+++ b/addons/portal/views/portal_templates.xml
@@ -0,0 +1,584 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <template id="frontend_layout" name="Main Frontend Layout" inherit_id="web.frontend_layout">
+ <xpath expr="//div[@id='wrapwrap']" position="attributes">
+ <attribute name="t-attf-class" add="#{request.env['res.lang']._lang_get(request.env.lang).direction == 'rtl' and 'o_rtl' or ''}" separator=" "/>
+ <attribute name="t-attf-class" add="#{'o_portal' if is_portal else ''}" separator=" "/>
+ </xpath>
+ <xpath expr="//div[@id='wrapwrap']/header/img" position="replace">
+ <nav class="navbar navbar-expand navbar-light bg-light">
+ <div class="container">
+ <a href="/" class="navbar-brand logo">
+ <img t-att-src="'/logo.png?company=%s' % res_company.id" t-att-alt="'Logo of %s' % res_company.name" t-att-title="res_company.name"/>
+ </a>
+ <ul id="top_menu" class="nav navbar-nav ml-auto">
+ <t t-call="portal.placeholder_user_sign_in">
+ <t t-set="_item_class" t-value="'nav-item'"/>
+ <t t-set="_link_class" t-value="'nav-link'"/>
+ </t>
+ <t t-call="portal.user_dropdown">
+ <t t-set="_user_name" t-value="true"/>
+ <t t-set="_item_class" t-value="'nav-item dropdown'"/>
+ <t t-set="_link_class" t-value="'nav-link'"/>
+ <t t-set="_dropdown_menu_class" t-value="'dropdown-menu-right'"/>
+ </t>
+ </ul>
+ </div>
+ </nav>
+ </xpath>
+ <xpath expr="//div[@id='wrapwrap']/main/t[@t-raw='0']" position="before">
+ <div t-if="o_portal_fullwidth_alert" class="alert alert-info alert-dismissible rounded-0 fade show d-print-none css_editable_mode_hidden">
+ <div class="container">
+ <t t-raw="o_portal_fullwidth_alert"/>
+ </div>
+ </div>
+ </xpath>
+ </template>
+
+ <!-- Added by another template so that it can be disabled if needed -->
+ <template id="footer_language_selector" inherit_id="portal.frontend_layout" name="Footer Language Selector">
+ <xpath expr="//*[hasclass('o_footer_copyright_name')]" position="after">
+ <t id="language_selector_call" t-call="portal.language_selector">
+ <t t-set="_div_classes" t-value="(_div_classes or '') + ' dropup'"/>
+ </t>
+ </xpath>
+ </template>
+
+ <template id="language_selector" name="Language Selector">
+ <t t-set="active_lang" t-value="list(filter(lambda lg : lg[0] == lang, languages))[0]"/>
+ <t t-set="language_selector_visible" t-value="len(languages) &gt; 1"/>
+ <div t-attf-class="js_language_selector #{_div_classes}" t-if="language_selector_visible">
+ <button class="btn btn-sm btn-outline-secondary border-0 dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
+ <span t-if="not no_text"
+ class="align-middle"
+ t-esc="active_lang[2].split('/').pop()"/>
+ </button>
+ <div class="dropdown-menu" role="menu">
+ <t t-foreach="languages" t-as="lg">
+ <a t-att-href="url_for(request.httprequest.path + '?' + keep_query(), lang_code=lg[0])"
+ class="dropdown-item js_change_lang"
+ t-att-data-url_code="lg[1]">
+ <span t-if="not no_text"
+ t-esc="lg[2].split('/').pop()"/>
+ </a>
+ </t>
+ </div>
+ </div>
+ </template>
+
+ <template id="user_dropdown" name="Portal User Dropdown">
+ <t t-set="is_connected" t-value="not user_id._is_public()"/>
+ <li t-if="is_connected" t-attf-class="#{_item_class} o_no_autohide_item">
+ <a href="#" role="button" data-toggle="dropdown" t-attf-class="dropdown-toggle #{_link_class}">
+ <t t-if="_avatar">
+ <t t-if="user_id.image_256" t-set="avatar_source" t-value="image_data_uri(user_id.image_256)"/>
+ <t t-else="" t-set="avatar_source" t-value="'/web/static/src/img/placeholder.png'"/>
+ <img t-att-src="avatar_source" t-attf-class="rounded-circle #{_avatar_class}" width="24" height="24" alt="" loading="eager"/>
+ </t>
+ <i t-if="_icon" t-attf-class="fa fa-1x fa-fw fa-user-circle-o #{_icon_class}"/>
+ <span t-if="_user_name" t-attf-class="#{_user_name_class}" t-esc="user_id.name[:23] + '...' if user_id.name and len(user_id.name) &gt; 25 else user_id.name"/>
+ </a>
+ <div t-attf-class="dropdown-menu js_usermenu #{_dropdown_menu_class}" role="menu">
+ <div id="o_logout_divider" class="dropdown-divider"/>
+ <a t-attf-href="/web/session/logout?redirect=/" role="menuitem" id="o_logout" class="dropdown-item">Logout</a>
+ </div>
+ </li>
+ </template>
+
+ <template id="portal_breadcrumbs" name="Portal Breadcrumbs">
+ <ol t-if="page_name != 'home'" class="o_portal_submenu breadcrumb mb-0 py-2 flex-grow-1 row">
+ <li class="breadcrumb-item ml-1"><a href="/my/home" aria-label="Home" title="Home"><i class="fa fa-home"/></a></li>
+ <li t-if="page_name == 'my_details'" class="breadcrumb-item">Details</li>
+ </ol>
+ </template>
+
+ <template id="portal_back_in_edit_mode" name="Back to edit mode">
+ <div t-ignore="true" class="text-center">
+ <t t-if="custom_html" t-raw="custom_html"/>
+ <t t-else="">This is a preview of the customer portal.</t>
+ <a t-att-href="backend_url"><i class="fa fa-arrow-right mr-1"/>Back to edit mode</a>
+ </div>
+ <button type="button" class="close" data-dismiss="alert" aria-label="Close"> &#215; </button>
+ </template>
+
+ <template id="portal_layout" name="Portal Layout">
+ <t t-call="portal.frontend_layout">
+ <t t-set="is_portal" t-value="True"/>
+
+ <div t-if="not no_breadcrumbs and not my_details and not breadcrumbs_searchbar" class="o_portal container mt-3">
+ <div class="row align-items-center bg-white no-gutters border rounded">
+ <div class="col-10">
+ <t t-call="portal.portal_breadcrumbs"></t>
+ </div>
+ <div t-if="prev_record or next_record" class="col-2 flex-grow-0 text-center">
+ <t t-call='portal.record_pager'/>
+ </div>
+ </div>
+ </div>
+ <div id="wrap" class='o_portal_wrap'>
+ <div class="container mb64">
+ <t t-if="my_details">
+ <div class="row justify-content-between mt-4">
+ <div t-attf-class="col-12 col-md col-lg-6">
+ <t t-raw="0"/>
+ </div>
+ <div id="o_my_sidebar" class="pt-3 pt-lg-0 col-12 col-md col-lg-4 col-xl-3 o_my_sidebar">
+ <div class="o_my_contact" t-if="sales_user">
+ <t t-call="portal.portal_contact"/>
+ </div>
+ <div class="o_portal_my_details">
+ <h4>Details <a role="button" href="/my/account" class="btn btn-sm btn-link"><i class="fa fa-pencil"/> Edit</a></h4>
+ <hr class="mt-1 mb-0"/>
+ <div t-field="user_id.partner_id" t-options='{"widget": "contact", "fields": ["email", "phone", "address", "name"]}'/>
+ </div>
+ <div class="o_portal_my_security mt-3">
+ <h4>Account Security </h4>
+ <hr class="mt-1 mb-1"/>
+ <a href="/my/security"><i class="fa fa-pencil mx-1"/>Edit Security Settings</a>
+ </div>
+ </div>
+ </div>
+ </t>
+ <t t-else="">
+ <t t-raw="0"/>
+ </t>
+ </div>
+ </div>
+ </t>
+ </template>
+
+ <template id="placeholder_user_sign_in" name="User Sign In Placeholder"/>
+
+ <template id="user_sign_in" name="User Sign In" inherit_id="portal.placeholder_user_sign_in">
+ <xpath expr="." position="inside">
+ <li groups="base.group_public" t-attf-class="#{_item_class} o_no_autohide_item">
+ <a t-attf-href="/web/login" t-attf-class="#{_link_class}">Sign in</a>
+ </li>
+ </xpath>
+ </template>
+
+ <template id="portal_my_home" name="My Portal">
+ <t t-call="portal.portal_layout">
+ <t t-set="my_details" t-value="True"/>
+ <div class="o_portal_my_home">
+ <div class="oe_structure" id="oe_structure_portal_my_home_1"/>
+ <h3>Documents</h3>
+ <div class="o_portal_docs list-group">
+ </div>
+ </div>
+ <div class="oe_structure" id="oe_structure_portal_my_home_2"/>
+ </t>
+ </template>
+
+ <template id="portal_docs_entry" name="My Portal Docs Entry">
+ <a t-att-href="url" t-att-title="title" class="list-group-item list-group-item-action d-flex align-items-center justify-content-between">
+ <t t-esc="title"/>
+ <t t-if='count'>
+ <span class="badge badge-secondary badge-pill" t-esc="count"/>
+ </t>
+ <t t-elif="placeholder_count">
+ <span class="badge badge-secondary badge-pill" t-att-data-placeholder_count="placeholder_count">
+ <i class="fa fa-spin fa-spinner"></i>
+ </span>
+ </t>
+ </a>
+ </template>
+
+ <template id="portal_table" name="My Portal Table">
+ <div t-attf-class="table-responsive border rounded border-top-0 #{classes if classes else ''}">
+ <table class="table rounded mb-0 bg-white o_portal_my_doc_table">
+ <t t-raw="0"/>
+ </table>
+ </div>
+ <div t-if="pager" class="o_portal_pager text-center">
+ <t t-call="portal.pager"/>
+ </div>
+ </template>
+
+ <template id="portal_record_sidebar" name="My Portal Record Sidebar">
+ <div t-attf-class="#{classes}">
+ <div class="card bg-white mb-4 sticky-top" id="sidebar_content">
+ <div t-if="title" class="card-body text-center pb-2 pt-3">
+ <t t-raw="title"/>
+ </div>
+ <t t-if="entries" t-raw="entries"/>
+ <div class="card-footer small text-center text-muted border-top-0 pt-1 pb-1 d-none d-lg-block">
+ Powered by <a target="_blank" href="http://www.odoo.com?utm_source=db&amp;utm_medium=portal" title="odoo"><img src="/web/static/src/img/logo.png" alt="Odoo Logo" height="15"/></a>
+ </div>
+ </div>
+ </div>
+ </template>
+
+ <!--
+ The search bar is composed of 2 buttons : a "filter by" and a "sort by". Changing the 'sortby'
+ criteria will keep the number of page, query params, ... Changing the 'filterby' param will
+ redirect the user to the beginning of document list, keeping query parameters.
+
+ These 2 buttons can be prepended by a advanced search input, to activate it, search_input need
+ to be initialized at 'True' and the content of the t-call is the list of li elements searchable.
+
+ :param dict searchbar_sortings : containing the sort criteria like
+ {'date': {'label': _('Newest'), 'order': 'create_date desc'}}
+ :param string sortby : name of the sort criteria
+ :param dict searchbar_filters : containing the filter criteria like
+ {'open': {'label': _('In Progress'), 'domain': [('state', '=', 'open')]}}
+ :param string filterby : name of the filter criteria
+ :param default_url : the base url of the pages (like '/my/orders')
+ :param boolean breadcrumbs_searchbar : set to True to show breadcrumbs rather than the title
+ :param boolean o_portal_search_panel : set to True to active the input search
+ :param html $0 : content of the t-call
+ :param title : bavbar title
+ :param classes : navbar classes
+ -->
+ <template id="portal_searchbar" name="Portal Search Bar">
+ <nav t-attf-class="navbar navbar-light navbar-expand-lg border py-0 mb-2 o_portal_navbar {{classes if classes else ''}} {{'mt-3 rounded' if breadcrumbs_searchbar else 'border-top-0' }}">
+ <!-- Navbar breadcrumb or title -->
+ <t t-if="breadcrumbs_searchbar">
+ <t t-call="portal.portal_breadcrumbs"/>
+ </t>
+ <span t-else="" class="navbar-brand mb-0 h1 mr-auto" t-esc="title or 'No title'"/>
+
+ <!-- Collapse button -->
+ <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#o_portal_navbar_content" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle filters">
+ <span class="navbar-toggler-icon small"/>
+ </button>
+
+ <!-- Collapsable content -->
+ <div class="collapse navbar-collapse" id="o_portal_navbar_content">
+ <div class="nav flex-column flex-lg-row ml-auto p-0 mb-3 mb-lg-0 mt-1 mt-lg-0">
+ <div t-if="searchbar_sortings" class="form-inline">
+ <span class="small mr-1 navbar-text">Sort By:</span>
+ <div class="btn-group">
+ <button id="portal_searchbar_sortby" data-toggle="dropdown" class="btn btn-secondary btn-sm dropdown-toggle">
+ <t t-esc="searchbar_sortings[sortby].get('label', 'Newest')"/>
+ </button>
+ <div class="dropdown-menu" aria-labelledby="portal_searchbar_sortby">
+ <t t-foreach="searchbar_sortings" t-as="option">
+ <a t-att-href="request.httprequest.path + '?' + keep_query('*', sortby=option)"
+ t-attf-class="dropdown-item#{sortby == option and ' active' or ''}">
+ <span t-esc="searchbar_sortings[option].get('label')"/>
+ </a>
+ </t>
+ </div>
+ </div>
+ </div>
+ <div t-if="searchbar_filters" class="form-inline ml-lg-2">
+ <span class="small mr-1 navbar-text">Filter By:</span>
+ <div class="btn-group">
+ <button id="portal_searchbar_filters" data-toggle="dropdown" class="btn btn-secondary btn-sm dropdown-toggle">
+ <t t-esc="searchbar_filters.get(filterby,searchbar_filters.get('all')).get('label', 'All')"/>
+ </button>
+ <div class="dropdown-menu" aria-labelledby="portal_searchbar_filters">
+ <t t-foreach="searchbar_filters" t-as="option">
+ <a t-att-href="default_url + '?' + keep_query('*', filterby=option)"
+ t-attf-class="dropdown-item#{filterby == option and ' active' or ''}">
+ <span t-esc="searchbar_filters[option].get('label')"/>
+ </a>
+ </t>
+ </div>
+ </div>
+ </div>
+ <div t-if="searchbar_groupby" class="form-inline ml-lg-2">
+ <span class="small mr-1 navbar-text">Group By:</span>
+ <div class="btn-group">
+ <button id="portal_searchbar_groupby" data-toggle="dropdown" class="btn btn-secondary btn-sm dropdown-toggle">
+ <t t-esc="searchbar_groupby[groupby].get('label', 'None')"/>
+ </button>
+ <div class="dropdown-menu" aria-labelledby="portal_searchbar_groupby">
+ <t t-foreach="searchbar_groupby" t-as="option">
+ <a t-att-href="default_url + '?' + keep_query('*', groupby=option)"
+ t-attf-class="dropdown-item#{groupby == option and ' active' or ''}">
+ <span t-esc="searchbar_groupby[option].get('label')"/>
+ </a>
+ </t>
+ </div>
+ </div>
+ </div>
+ <t t-raw="0"/>
+ </div>
+ <form t-if="searchbar_inputs" class="form-inline o_portal_search_panel ml-lg-4 col-xl-4 col-md-5">
+ <div class="input-group input-group-sm w-100">
+ <div class="input-group-prepend">
+ <button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown"/>
+ <div class="dropdown-menu" role="menu">
+ <t t-foreach='searchbar_inputs' t-as='input'>
+ <a t-att-href="'#' + searchbar_inputs[input]['input']"
+ t-attf-class="dropdown-item#{search_in == searchbar_inputs[input]['input'] and ' active' or ''}">
+ <span t-raw="searchbar_inputs[input]['label']"/>
+ </a>
+ </t>
+ </div>
+ </div>
+ <input type="text" class="form-control form-control-sm" placeholder="Search" t-att-value='search' name="search"/>
+ <span class="input-group-append">
+ <button class="btn btn-secondary search-submit" type="button">
+ <span class="fa fa-search"/>
+ </button>
+ </span>
+ </div>
+ </form>
+ </div>
+ </nav>
+ </template>
+
+ <template id="portal_record_layout" name="Portal single record layout">
+ <div t-attf-class="card mt-0 border-top-0 rounded-0 rounded-bottom #{classes if classes else ''}">
+ <div t-if="card_header" t-attf-class="card-header #{header_classes if header_classes else ''}">
+ <t t-raw="card_header"/>
+ </div>
+ <div t-if="card_body" t-attf-class="card-body #{body_classes if body_classes else ''}">
+ <t t-raw="card_body"/>
+ </div>
+ </div>
+ </template>
+
+ <template id="portal_contact" name="Contact">
+ <div class="o_portal_contact_details mb-5">
+ <h4><t t-if="title" t-esc="title"/><t t-else="">Your contact</t></h4>
+ <hr class="mt-1 mb0"/>
+ <h6 class="mb-1"><b t-esc="sales_user.name"/></h6>
+ <div class="d-flex align-items-center mb-1">
+ <div class="fa fa-envelope fa-fw mr-1"></div>
+ <a t-att-href="'mailto:'+sales_user.email" t-esc="sales_user.email"/>
+ </div>
+ <div class="d-flex flex-nowrap align-items-center mb-1">
+ <div class="fa fa-phone fa-fw mr-1"></div>
+ <span t-esc="sales_user.phone"/>
+ </div>
+ <div class="d-flex flex-nowrap align-items-center mb-1">
+ <div class="fa fa-map-marker fa-fw mr-1"></div>
+ <span t-esc="sales_user.city"/>
+ </div>
+ </div>
+ </template>
+
+ <template id="portal_my_details">
+ <t t-call="portal.portal_layout">
+ <t t-set="additional_title">Contact Details</t>
+ <form action="/my/account" method="post">
+ <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
+ <div class="row o_portal_details">
+ <div class="col-lg-8">
+ <div class="row">
+ <t t-set="partner_can_edit_vat" t-value="partner.can_edit_vat()"/>
+ <div class="col-lg-12">
+ <div t-if="error_message" class="alert alert-danger" role="alert">
+ <t t-foreach="error_message" t-as="err"><t t-esc="err"/><br /></t>
+ </div>
+ </div>
+ <div t-attf-class="form-group #{error.get('name') and 'o_has_error' or ''} col-xl-6">
+ <label class="col-form-label" for="name">Name</label>
+ <input type="text" name="name" t-attf-class="form-control #{error.get('name') and 'is-invalid' or ''}" t-att-value="name or partner.name" />
+ </div>
+ <div t-attf-class="form-group #{error.get('email') and 'o_has_error' or ''} col-xl-6">
+ <label class="col-form-label" for="email">Email</label>
+ <input type="email" name="email" t-attf-class="form-control #{error.get('email') and 'is-invalid' or ''}" t-att-value="email or partner.email" />
+ </div>
+
+ <div class="clearfix" />
+ <div t-attf-class="form-group #{error.get('company_name') and 'o_has_error' or ''} col-xl-6">
+ <label class="col-form-label label-optional" for="company_name">Company Name</label>
+ <!-- The <input> is replace by a <p> to avoid sending an unauthorized value on form submit.
+ The user might not have rights to change company_name but should still be able to see it.
+ -->
+ <p t-if="not partner_can_edit_vat" t-attf-class="form-control" readonly="1" t-esc="partner.commercial_company_name" title="Changing company name is not allowed once document(s) have been issued for your account. Please contact us directly for this operation."/>
+ <input t-else="" type="text" name="company_name" t-attf-class="form-control #{error.get('company_name') and 'is-invalid' or ''}" t-att-value="company_name or partner.commercial_company_name"/>
+ </div>
+ <div t-attf-class="form-group #{error.get('vat') and 'o_has_error' or ''} col-xl-6">
+ <label class="col-form-label label-optional" for="vat">VAT Number</label>
+ <t t-set="vat_not_editable_message">Changing VAT number is not allowed once document(s) have been issued for your account. Please contact us directly for this operation.</t>
+ <input type="text" name="vat" t-attf-class="form-control #{error.get('vat') and 'is-invalid' or ''}" t-att-value="vat or partner.vat" t-att-readonly="None if partner_can_edit_vat else '1'" t-att-title="None if partner_can_edit_vat else vat_not_editable_message" />
+ </div>
+ <div t-attf-class="form-group #{error.get('phone') and 'o_has_error' or ''} col-xl-6">
+ <label class="col-form-label" for="phone">Phone</label>
+ <input type="tel" name="phone" t-attf-class="form-control #{error.get('phone') and 'is-invalid' or ''}" t-att-value="phone or partner.phone" />
+ </div>
+
+ <div class="clearfix" />
+ <div t-attf-class="form-group #{error.get('street') and 'o_has_error' or ''} col-xl-6">
+ <label class="col-form-label" for="street">Street</label>
+ <input type="text" name="street" t-attf-class="form-control #{error.get('street') and 'is-invalid' or ''}" t-att-value="street or partner.street"/>
+ </div>
+ <div t-attf-class="form-group #{error.get('city') and 'o_has_error' or ''} col-xl-6">
+ <label class="col-form-label" for="city">City</label>
+ <input type="text" name="city" t-attf-class="form-control #{error.get('city') and 'is-invalid' or ''}" t-att-value="city or partner.city" />
+ </div>
+ <div t-attf-class="form-group #{error.get('zip') and 'o_has_error' or ''} col-xl-6">
+ <label class="col-form-label label-optional" for="zipcode">Zip / Postal Code</label>
+ <input type="text" name="zipcode" t-attf-class="form-control #{error.get('zip') and 'is-invalid' or ''}" t-att-value="zipcode or partner.zip" />
+ </div>
+ <div t-attf-class="form-group #{error.get('country_id') and 'o_has_error' or ''} col-xl-6">
+ <label class="col-form-label" for="country_id">Country</label>
+ <select name="country_id" t-attf-class="form-control #{error.get('country_id') and 'is-invalid' or ''}">
+ <option value="">Country...</option>
+ <t t-foreach="countries or []" t-as="country">
+ <option t-att-value="country.id" t-att-selected="country.id == int(country_id) if country_id else country.id == partner.country_id.id">
+ <t t-esc="country.name" />
+ </option>
+ </t>
+ </select>
+ </div>
+ <div t-attf-class="form-group #{error.get('state_id') and 'o_has_error' or ''} col-xl-6">
+ <label class="col-form-label label-optional" for="state_id">State / Province</label>
+ <select name="state_id" t-attf-class="form-control #{error.get('state_id') and 'is-invalid' or ''}">
+ <option value="">select...</option>
+ <t t-foreach="states or []" t-as="state">
+ <option t-att-value="state.id" style="display:none;" t-att-data-country_id="state.country_id.id" t-att-selected="state.id == partner.state_id.id">
+ <t t-esc="state.name" />
+ </option>
+ </t>
+ </select>
+ </div>
+ <input type="hidden" name="redirect" t-att-value="redirect"/>
+ </div>
+ <div class="clearfix">
+ <button type="submit" class="btn btn-primary float-right mb32 ">
+ Confirm
+ <span class="fa fa-long-arrow-right" />
+ </button>
+ </div>
+ </div>
+ </div>
+ </form>
+ </t>
+ </template>
+
+ <template id="portal_my_security">
+ <t t-call="portal.portal_layout"><div class="o_portal_security_body">
+ <t t-set="additional_title">Security</t>
+ <t t-set="no_breadcrumbs" t-value="1"/>
+ <div class="alert alert-danger" role="alert" t-if="get_error(errors)">
+ <t t-esc="errors"/>
+ </div>
+ <section>
+ <h3>Change Password</h3>
+ <t t-set="path">password</t>
+ <div class="alert alert-success" role="alert" t-if="success and success.get('password')">
+ Password Updated!
+ </div>
+ <div class="alert alert-danger" role="alert" t-if="get_error(errors, 'password')">
+ <t t-esc="errors['password']"/>
+ </div>
+ <form action="/my/security" method="post" class="oe_reset_password_form">
+ <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
+ <input type="hidden" name="op" value="password"/>
+ <div class="form-group">
+ <label for="current">Password:</label>
+ <input type="password" t-attf-class="form-control form-control-sm {{ 'is-invalid' if get_error(errors, 'password.old') else '' }}"
+ id="current" name="old"
+ autocomplete="current-password" required="required"/>
+ <div class="invalid-feedback">
+ <t t-esc="get_error(errors, 'password.old')"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="new">New Password:</label>
+ <input type="password" t-attf-class="form-control form-control-sm {{ 'is-invalid' if get_error(errors, 'password.new1') else '' }}"
+ id="new" name="new1"
+ autocomplete="new-password" required="required"/>
+ <div class="invalid-feedback">
+ <t t-esc="get_error(errors, 'password.new1')"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="new2">Verify New Password:</label>
+ <input type="password" t-attf-class="form-control form-control-sm {{ 'is-invalid' if get_error(errors, 'password.new2') else '' }}"
+ id="new2" name="new2"
+ autocomplete="new-password" required="required"/>
+ <div class="invalid-feedback">
+ <t t-esc="get_error(errors, 'password.new2')"/>
+ </div>
+ </div>
+ <button type="submit" class="btn btn-danger">Change Password</button>
+ </form>
+ </section>
+ </div></t>
+ </template>
+
+ <template id="record_pager" name="Portal Record Pager">
+ <t t-if='prev_record or next_record'>
+ <div class="record_pager btn-group" role="group">
+ <a role="button" t-att-class="'btn btn-link %s' % ('disabled' if not prev_record else '')" t-att-href="prev_record or '#'" ><i class="fa fa-chevron-left" role="img" aria-label="Previous" title="Previous"></i></a>
+ <a role="button" t-att-class="'btn btn-link %s' % ('disabled' if not next_record else '')" t-att-href="next_record or '#'" ><i class="fa fa-chevron-right" role="img" aria-label="Next" title="Next"></i></a>
+ </div>
+ </t>
+ </template>
+
+ <template id="pager" name="Pager">
+ <ul t-if="pager['page_count'] > 1" t-attf-class="#{ classname or '' } pagination m-0 #{_classes}" t-att-style="style or None">
+ <li t-attf-class="page-item #{'disabled' if pager['page']['num'] == 1 else ''}">
+ <a t-att-href=" pager['page_previous']['url'] if pager['page']['num'] != 1 else None" t-attf-class="page-link #{extraLinkClass}">Prev</a>
+ </li>
+ <t t-foreach="pager['pages']" t-as="page">
+ <li t-attf-class="page-item #{'active' if page['num'] == pager['page']['num'] else ''}"> <a t-att-href="page['url']" t-attf-class="page-link #{extraLinkClass}" t-raw="page['num']"></a></li>
+ </t>
+ <li t-attf-class="page-item #{'disabled' if pager['page']['num'] == pager['page_count'] else ''}">
+ <a t-att-href="pager['page_next']['url'] if pager['page']['num'] != pager['page_count'] else None" t-attf-class="page-link #{extraLinkClass}">Next</a>
+ </li>
+ </ul>
+ </template>
+
+ <template id="my_account_link" name="Link to frontend portal" inherit_id="portal.user_dropdown">
+ <xpath expr="//*[@id='o_logout_divider']" position="before">
+ <a href="/my/home" role="menuitem" class="dropdown-item">My Account</a>
+ </xpath>
+ </template>
+
+ <!--
+ Generic chatter template for the frontend
+ This template provide the container of the chatter. The rest is done in js.
+ To use this template, you need to call it after setting the following variable in your template or in your controller:
+ :object browserecord : the mail_thread object
+ :message_per_page int (optional): number of message per chatter page
+ :token string (optional): if you want your chatter to be available for non-logged user,
+ you can use a token to verify the identity of the user;
+ the message will be posted with the identity of the partner_id of the object
+ :hash : signed token with the partner_id using `_sign_token` method (on mail.thread)
+ :pid : identifier of the partner signing the token
+ -->
+ <template id="message_thread">
+ <div id="discussion" data-anchor="true" class="d-print-none o_portal_chatter o_not_editable p-0"
+ t-att-data-token="token" t-att-data-res_model="object._name" t-att-data-pid="pid" t-att-data-hash="hash" t-att-data-res_id="object.id" t-att-data-pager_step="message_per_page or 10" t-att-data-allow_composer="'0' if disable_composer else '1'">
+ </div>
+ </template>
+
+ <!--
+ Snippet to request user signature in the portal. The feature comes with
+ the JS file `portal_signature.js`.
+
+ The following variable has to be set:
+ - {string} call_url: url where to send the name and signature by RPC
+ The url should contain a query string if additional parameters
+ have to be sent, such as an access token.
+
+ The following variables are optional:
+ - {string} default_name: the default name to display
+ - {string} mode: 'draw', 'auto', or 'load'
+ - {string} send_label: label of the send button
+ - {number} signature_ratio: ratio of the signature area
+ - {string} signature_type: 'signature' or 'initial'
+
+ For the default values and more information, see init() of the widgets
+ SignatureForm and NameAndSignature.
+ -->
+ <template id="portal.signature_form" name="Ask Signature">
+ <div class="o_portal_signature_form"
+ t-att-data-call-url="call_url"
+ t-att-data-default-name="default_name"
+ t-att-data-mode="mode"
+ t-att-data-send-label="send_label"
+ t-att-data-signature-ratio="signature_ratio"
+ t-att-data-signature-type="signature_type"
+ t-att-data-font-color="font_color"
+ />
+ </template>
+
+ <template id="portal_sidebar" name="Sidebar">
+ <t t-call="portal.portal_layout">
+ <body data-spy="scroll" data-target=".navspy" data-offset="50">
+ <div class="container o_portal_sidebar"></div>
+ <div class="oe_structure mb32" id="oe_structure_portal_sidebar_1"/>
+ </body>
+ </t>
+ </template>
+</odoo>