Skip to content

Portal Module

The Portal module provides a customer/vendor self-service interface where external users can view their documents (invoices, orders, etc.) without needing backend access.

The portal system consists of:

  1. PortalAction - Configuration records that define which models are accessible in the portal
  2. Portal Views - PortalList and PortalDetail view types for displaying documents
  3. WebBlocks - Jinja templates for rendering portal pages
  4. Theme Integration - Inherits styling from website theme when installed

To expose a model in the customer portal, create a PortalAction record:

views/portal.yaml
- data_type: PortalAction
identifier: portal_invoices
name: Invoices
model: FinancialDocument
module: invoicing
icon: bi-receipt
sequence: 20
contact_field: contact
global_filter: "Q(type__in=['Customer Invoice', 'Credit Note'])"
list_view: invoice_portal_list
detail_view: invoice_portal_detail
active: true
FieldTypeDescription
identifierstringUnique identifier (used in URL: /portal/{identifier})
namestringDisplay name in navigation sidebar
modelstringTarget model name
modulestringModule that owns this portal action
iconstringBootstrap icon class (e.g., bi-receipt)
sequenceintegerOrder in navigation (lower = first)
contact_fieldstringField linking records to portal user’s contact
global_filterstringQ expression to filter records (e.g., only show certain types)
list_viewstringIdentifier of PortalList view
detail_viewstringIdentifier of PortalDetail view
activebooleanWhether this portal action is enabled

Portal views use the same UiView system as backend views but with types PortalList and PortalDetail.

Displays a table of records with search, sorting, and pagination.

- data_type: UiView
name: invoice_portal_list
identifier: invoice_portal_list
type: PortalList
model: FinancialDocument
arch:
- content:
- type: field
name: number
properties:
widget: Text
label: Number
fw: 600
- type: field
name: date
properties:
widget: Date
label: Date
- type: field
name: state
properties:
widget: Badge
label: Status
colors:
Draft: gray
Posted: green
Cancelled: red
- type: field
name: total_amount
properties:
widget: Monetary
label: Total
fw: 600

The following field names are automatically sortable: name, date, state, total, number, created_date.

Displays a single document with structured layout. The architecture supports:

  • Info Panel Fields - Displayed in the left sidebar
  • Action Buttons - Displayed in the left sidebar
  • Document Content - Rows, columns, cards, and fields
- data_type: UiView
name: invoice_portal_detail
identifier: invoice_portal_detail
type: PortalDetail
model: FinancialDocument
arch:
# Info panel fields (sidebar)
- type: field
name: total_amount
properties:
display_mode: info_panel
label: Total Amount
widget: Monetary
# Action buttons (sidebar)
- type: actionButton
properties:
label: Download PDF
icon: bi-download
report: financial_document_print
variant: secondary
# Document header
- type: row
content:
- type: column
span: 8
content:
- type: field
name: number
properties:
widget: Text
size: xl
fw: 700
noLabel: true
- type: column
span: 4
properties:
style: "text-align: right;"
content:
- type: field
name: state
properties:
widget: Badge
noLabel: true
# Content cards
- type: card
title: Invoice Details
icon: bi-info-circle
content:
- type: row
content:
- type: column
span: 3
content:
- type: field
name: date
properties:
widget: Date
label: Invoice Date
# Lines with totals
- type: field
name: lines
properties:
widget: List
view: invoice_line_portal_list
title: Invoice Lines
icon: bi-receipt
totals:
- name: subtotal
label: Subtotal
widget: Monetary
- name: total_amount
label: Total
widget: Monetary
fw: 600
# Messages section
- type: messages
ElementDescription
rowHorizontal container (12-column grid)
columnVertical container with span (1-12)
cardCard container with title and optional icon
dividerVisual separator

Columns support generic style and class properties for flexibility:

- type: column
span: 4
properties:
style: "text-align: right;"
class: "custom-class"
content:
- type: field
name: state
ElementDescription
fieldData field with widget
actionButtonButton for actions (download, sign, etc.)
messagesChatter/messaging section
PropertyDescription
display_mode: info_panelShow field in sidebar info panel
noLabel: trueHide field label
visible: Q(...)Conditional visibility using Q expressions
fw: 600Font weight (400, 500, 600, 700)
size: xlText size (sm, md, lg, xl)

Portal views support these widgets:

WidgetDescription
TextPlain text display
DateFormatted date
BadgeStatus badge with colors
MonetaryCurrency-formatted amount
NumberNumeric value
HtmlHTML content
ListEmbedded list (for lines)
- type: field
name: state
properties:
widget: Badge
colors:
Draft: gray
Posted: green
Paid: teal
Cancelled: red

For order/invoice lines, use the List widget with embedded totals:

- type: field
name: lines
properties:
widget: List
view: sale_order_line_portal_list
title: Order Lines
icon: bi-box-seam
totals:
- name: subtotal
label: Subtotal
widget: Monetary
- name: total_tax
label: Tax
widget: Monetary
- name: total
label: Total
widget: Monetary
fw: 600

Fields marked with display_mode: info_panel appear in the left sidebar:

- type: field
name: total_amount
properties:
display_mode: info_panel
label: Total Amount
widget: Monetary

Monetary fields display prominently. Other field types display as label/value pairs.

Action buttons appear in the sidebar and can trigger:

- type: actionButton
properties:
label: Download PDF
icon: bi-download
report: sale_order_print
variant: secondary
- type: actionButton
properties:
label: Confirm Order
icon: bi-check
method: action_confirm
variant: primary
confirm: "Are you sure you want to confirm?"
- type: actionButton
properties:
label: Accept & Sign
icon: bi-pen
method: action_confirm
modal: signature
variant: primary
visible: Q(state__eq='Draft')
VariantDescription
primaryProminent action (dark background)
secondaryStandard action (outlined)
successPositive action (green)

Use Q expressions for conditional visibility on fields, cards, and actions:

# Show only when state is Draft
visible: Q(state__eq='Draft')
# Show only when field has value
visible: Q(terms_conditions__isnull=False) & Q(terms_conditions__neq='')
# Show for Posted invoices
visible: Q(state__eq='Posted')

Add a messaging/chatter section to allow portal users to communicate:

- type: messages

This renders the document’s message history and allows adding new messages.

Cards can have visibility conditions:

- type: card
title: Terms & Conditions
icon: bi-file-text
properties:
visible: Q(terms_conditions__isnull=False) & Q(terms_conditions__neq='')
content:
- type: field
name: terms_conditions
properties:
widget: Html
noLabel: true

The portal automatically integrates with the website theme when installed:

  • Uses theme CSS variables (--theme-primary, --theme-background, etc.)
  • Inherits navigation header and footer
  • Falls back to standalone portal header when website module is not installed

Portal styles use these CSS variables with theme fallbacks:

:root {
--portal-accent: var(--theme-primary, #1a1a1a);
--portal-background: var(--theme-background, #fafafa);
--portal-surface: var(--theme-surface, #ffffff);
--portal-border: var(--theme-border, #dee2e6);
--portal-text: var(--theme-text, #212529);
--portal-text-muted: var(--theme-text_muted, #6c757d);
}

Here’s a complete portal configuration for a Sales Order:

views/portal.yaml
- depends: portal_views
data_type: PortalAction
identifier: portal_orders
name: Orders
model: SaleOrder
module: sales
icon: bi-cart
sequence: 10
contact_field: contact
list_view: sale_order_portal_list
detail_view: sale_order_portal_detail
active: true
views/portal_views.yaml
- data_type: UiView
name: sale_order_portal_list
identifier: sale_order_portal_list
type: PortalList
model: SaleOrder
arch:
- content:
- type: field
name: name
properties:
widget: Text
label: Number
fw: 600
- type: field
name: date
properties:
widget: Date
label: Date
- type: field
name: state
properties:
widget: Badge
label: Status
colors:
Draft: gray
Confirmed: green
Done: teal
Cancelled: red
- type: field
name: total
properties:
widget: Monetary
label: Total
fw: 600
- data_type: UiView
name: sale_order_portal_detail
identifier: sale_order_portal_detail
type: PortalDetail
model: SaleOrder
arch:
# Sidebar: Info panel
- type: field
name: user
properties:
display_mode: info_panel
label: Your Contact
- type: field
name: total
properties:
display_mode: info_panel
label: Total Amount
widget: Monetary
# Sidebar: Actions
- type: actionButton
properties:
label: Download PDF
icon: bi-download
report: sale_order_print
variant: secondary
- type: actionButton
properties:
label: Accept & Sign
icon: bi-pen
method: action_confirm
modal: signature
variant: primary
visible: Q(state__eq='Draft')
# Header row
- type: row
content:
- type: column
span: 8
content:
- type: field
name: name
properties:
widget: Text
size: xl
fw: 700
noLabel: true
- type: column
span: 4
properties:
style: "text-align: right;"
content:
- type: field
name: state
properties:
widget: Badge
noLabel: true
colors:
Draft: gray
Confirmed: green
Done: teal
Cancelled: red
# Order details card
- type: card
title: Order Details
icon: bi-info-circle
content:
- type: row
content:
- type: column
span: 3
content:
- type: field
name: date
properties:
widget: Date
label: Order Date
- type: column
span: 3
content:
- type: field
name: validity_date
properties:
widget: Date
label: Valid Until
visible: Q(state__eq='Draft')
- type: column
span: 3
content:
- type: field
name: commitment_date
properties:
widget: Date
label: Delivery Date
- type: column
span: 3
content:
- type: field
name: payment_terms
properties:
widget: Text
label: Payment Terms
# Order lines with totals
- type: field
name: lines
properties:
widget: List
view: sale_order_line_portal_list
title: Order Lines
icon: bi-box-seam
totals:
- name: subtotal
label: Subtotal
widget: Monetary
- name: total_tax
label: Tax
widget: Monetary
- name: total
label: Total
widget: Monetary
fw: 600
# Terms & Conditions (conditional)
- type: card
title: Terms & Conditions
icon: bi-file-text
properties:
visible: Q(terms_conditions__isnull=False) & Q(terms_conditions__neq='')
content:
- type: field
name: terms_conditions
properties:
widget: Html
noLabel: true
# Messages
- type: messages
- data_type: UiView
name: sale_order_line_portal_list
identifier: sale_order_line_portal_list
type: PortalList
model: SaleOrderLine
arch:
- content:
- type: field
name: description
properties:
widget: Text
label: Item
- type: field
name: quantity
properties:
widget: Number
label: Qty
- type: field
name: unit_price
properties:
widget: Monetary
label: Unit Price
- type: field
name: subtotal
properties:
widget: Monetary
label: Amount

Portal URLs follow this pattern:

URLDescription
/portalPortal home page
/portal/{identifier}Document list
/portal/{identifier}/{id}Document detail
/portal/{identifier}/{id}/report/{report_id}PDF report download
/portal/{identifier}/{id}/action/{method}Execute action

Portal access is controlled by:

  1. Authentication - Users must be logged in with portal access
  2. Contact Filtering - Records are filtered by contact_field to show only the user’s documents
  3. Global Filter - Additional Q expression filtering (e.g., only show customer invoices, not vendor bills)
  4. Model Collaboration - The model should have _collaborate = True and _collaborate_contact configured