Other View Types
Calendar Views
Section titled “Calendar Views”Display date-based records on a calendar.
Basic Calendar
Section titled “Basic Calendar”{ "data_type": "UiView", "identifier": "meeting_calendar_view", "type": "Calendar", "model": "Meeting", "arch": [ { "content": [ { "type": "field", "name": "name", "display_mode": "title" }, { "type": "field", "name": "start_time", "display_mode": "start" }, { "type": "field", "name": "end_time", "display_mode": "end" } ] } ]}Display Modes
Section titled “Display Modes”| Mode | Description |
|---|---|
title | Event title |
start | Start datetime |
end | End datetime |
With Quick Create
Section titled “With Quick Create”{ "content": [ {"type": "field", "name": "name", "display_mode": "title"}, {"type": "field", "name": "start_time", "display_mode": "start"}, {"type": "field", "name": "end_time", "display_mode": "end"}, { "type": "QuickCreate", "context": {"form_identifier": "meeting_quick_create_form"} } ]}Search Views
Section titled “Search Views”Define filters and groupings for list/kanban views.
Basic Search
Section titled “Basic Search”{ "data_type": "UiView", "identifier": "contact_search_view", "type": "Search", "model": "Contact", "arch": [ { "type": "filter", "identifier": "customers", "description": "Customers", "filter": "(Q(is_customer=True))" }, { "type": "filter", "identifier": "suppliers", "description": "Suppliers", "filter": "(Q(is_supplier=True))" }, { "type": "group", "identifier": "group_country", "name": "country", "description": "Country" } ]}Filters
Section titled “Filters”Predefined filter conditions:
{ "type": "filter", "identifier": "active_only", "description": "Active", "filter": "(Q(active=True))"}Filter Syntax
Section titled “Filter Syntax”"(Q(field='value'))" # String equality (shorthand)"(Q(field__eq='value'))" # String equality (explicit)"(Q(field=True))" # Boolean check"(Q(field__isnull=True))" # ManyToOne null check (use field name, not field_id)"(Q(stage__is_won=True))" # Related field lookup (traverse relation)"(Q(active=True) & Q(status='open'))" # AND condition"(Q(user__id__eq=uid))" # Current user - ManyToOne requires field__id__eq=uid"(Q(company__id__eq=cid))" # Current company - ManyToOne requires field__id__eq=cidImportant for ManyToOne fields with uid/cid:
- Use
field__id__eq=uidsyntax (NOTfield_id=uidorfield=uid) - This traverses the relationship to compare the related record’s ID
Available Variables
Section titled “Available Variables”| Variable | Description |
|---|---|
uid | Current logged-in user’s ID |
cid | Current active company ID (user’s selected company for the session) |
D() | Dynamic date helper (see below) |
Dynamic Date Helper D()
Section titled “Dynamic Date Helper D()”The D() helper generates dynamic dates for use in filters. It returns a date or datetime object that is evaluated at query time.
Basic usage:
D('today') # today's dateD('now') # current datetimeRelative days/weeks/months/years:
D('today', days=-30) # 30 days agoD('today', days=7) # 7 days from nowD('today', weeks=-2) # 2 weeks agoD('today', months=-3) # 3 months agoD('today', years=-1) # 1 year agoPeriod boundaries (offset: 0=current, -1=previous, 1=next):
| Expression | Description |
|---|---|
D('start_of_week', 0) | Monday of this week |
D('end_of_week', 0) | Sunday of this week |
D('start_of_month', 0) | 1st of this month |
D('end_of_month', 0) | Last day of this month |
D('start_of_month', -1) | Start of last month |
D('end_of_month', -1) | End of last month |
D('start_of_quarter', 0) | Start of this quarter |
D('end_of_quarter', 1) | End of next quarter |
D('start_of_year', 0) | Jan 1st this year |
D('end_of_year', -1) | Dec 31st last year |
Example filters using D():
{ "type": "filter", "identifier": "overdue", "description": "Overdue", "filter": "(Q(state='Posted') & Q(due_date__lt=D('today')))"}{ "type": "filter", "identifier": "due_this_week", "description": "Due This Week", "filter": "(Q(due_date__gte=D('start_of_week', 0)) & Q(due_date__lte=D('end_of_week', 0)))"}{ "type": "filter", "identifier": "last_30_days", "description": "Last 30 Days", "filter": "(Q(create_date__gte=D('today', days=-30)))"}{ "type": "filter", "identifier": "this_quarter", "description": "This Quarter", "filter": "(Q(date__gte=D('start_of_quarter', 0)) & Q(date__lte=D('end_of_quarter', 0)))"}Supported Operators
Section titled “Supported Operators”| Operator | Example | Description |
|---|---|---|
= or __eq | Q(state='Pending') | Exact match |
__neq | Q(state__neq='Draft') | Not equal |
__isnull | Q(user__isnull=True) | Null check (use field name, not field_id) |
__gt | Q(amount__gt=100) | Greater than |
__gte | Q(amount__gte=100) | Greater than or equal |
__lt | Q(amount__lt=100) | Less than |
__lte | Q(amount__lte=100) | Less than or equal |
__in | Q(state__in=['Draft','Pending']) | Value in list |
__nin | Q(state__nin=['Done']) | Value not in list |
__contains | Q(name__contains='test') | Contains (case-sensitive) |
__icontains | Q(name__icontains='test') | Contains (case-insensitive) |
__iexact | Q(name__iexact='test') | Case-insensitive exact |
Related Field Lookups
Section titled “Related Field Lookups”For ManyToOne fields, you can filter by related record’s fields using __fieldname:
{ "type": "filter", "identifier": "won_filter", "description": "Won", "filter": "(Q(stage__is_won=True))"}Note: When filtering by user-definable data (like stages), use semantic boolean flags (e.g., is_won, is_lost) rather than names or identifiers which users can change.
Groups
Section titled “Groups”Grouping options for the view:
{ "type": "group", "identifier": "group_stage", "name": "stage", "description": "Stage"}Browse Panel
Section titled “Browse Panel”The browse panel provides a sidebar for faceted filtering. It displays field values with record counts, allowing users to quickly filter by clicking on values.
Basic Browse Panel
Section titled “Basic Browse Panel”Add browsepanel elements to your search view:
{ "data_type": "UiView", "identifier": "contact_search_view", "type": "Search", "model": "Contact", "arch": [ { "type": "filter", "identifier": "customers", "description": "Customers", "filter": "(Q(is_customer=True))" }, { "type": "browsepanel", "name": "company_type", "description": "Type", "expanded": true }, { "type": "browsepanel", "name": "categories", "description": "Categories" }, { "type": "browsepanel", "name": "country", "description": "Country" } ]}Browse Panel Properties
Section titled “Browse Panel Properties”| Property | Type | Default | Description |
|---|---|---|---|
name | string | required | Field name to browse by |
description | string | field label | Display label in the panel |
expanded | boolean | false | Whether section is expanded by default |
limit | number | 8 | Maximum values to show before “Show more” |
Supported Field Types
Section titled “Supported Field Types”The browse panel supports the following field types:
| Field Type | Behavior |
|---|---|
ManyToOne | Shows related record names with counts |
ManyToMany | Shows related record names with counts |
OneToOne | Shows related record names with counts |
Selection | Shows selection choices with counts |
How It Works
Section titled “How It Works”- Multi-select: Users can select multiple values within a field (OR logic)
- Cross-field AND: Selections across different fields use AND logic
- Dynamic counts: Record counts update based on other active filters
- Self-exclusion: A facet is never narrowed by its own selection — selecting a value keeps that facet fully browsable while still cross-narrowing the others, so the counts stay meaningful
- Toggle visibility: Users can show/hide the panel via the toolbar button
Hierarchical (Drill-Down) Browse
Section titled “Hierarchical (Drill-Down) Browse”When a browsepanel field is a ManyToOne/ManyToMany whose target model is a
tree (it opts into the hierarchy primitive
via _parent_field), the panel automatically renders that facet as an
expandable, multi-level tree instead of a flat list. No extra view configuration
is needed.
- Nodes nest under their parents to any depth, with per-node expand/collapse.
- Counts are rolled up the tree, so a parent shows the total for itself plus all descendants.
- Selecting a node filters the record list to that node and its entire
subtree (matched via the materialised
parent_path), so picking a top-level category captures everything beneath it. - Only branches that lead to matching records appear; an ancestor with no direct records is still shown so users can drill through it.
For example, a category browse field on Product becomes a drill-down tree as
soon as ProductCategory declares _parent_field = "parent" — the search view
entry stays exactly the same {"type": "browsepanel", "name": "category"}.
Example with Multiple Fields
Section titled “Example with Multiple Fields”{ "data_type": "UiView", "identifier": "product_search_view", "type": "Search", "model": "Product", "arch": [ { "type": "browsepanel", "name": "category", "description": "Category", "expanded": true }, { "type": "browsepanel", "name": "brand", "description": "Brand" }, { "type": "browsepanel", "name": "product_type", "description": "Type", "limit": 5 }, { "type": "filter", "identifier": "in_stock", "description": "In Stock", "filter": "(Q(quantity__gt=0))" } ]}Complete Search Example
Section titled “Complete Search Example”{ "data_type": "UiView", "identifier": "lead_search_view", "type": "Search", "model": "CrmLead", "arch": [ { "type": "filter", "identifier": "my_opportunities", "description": "My Opportunities", "filter": "(Q(user__id__eq=uid))" }, { "type": "filter", "identifier": "unassigned", "description": "Unassigned", "filter": "(Q(user_id__isnull=True))" }, { "type": "filter", "identifier": "won_filter", "description": "Won", "filter": "(Q(stage__is_won=True))" }, { "type": "filter", "identifier": "lost_filter", "description": "Lost", "filter": "(Q(stage__is_lost=True))" }, { "type": "group", "identifier": "group_stage", "name": "stage", "description": "Stage" }, { "type": "group", "identifier": "group_user", "name": "user", "description": "Salesperson" }, { "type": "group", "identifier": "group_source", "name": "source", "description": "Source" } ]}Wizard Views
Section titled “Wizard Views”Wizard views define the UI for transient models (wizards). Wizards are temporary forms used for user input before executing an action.
Basic Wizard
Section titled “Basic Wizard”{ "data_type": "UiView", "identifier": "record_payment_wizard_view", "type": "Wizard", "model": "RecordPaymentWizard", "arch": [ { "type": "row", "content": [ { "type": "column", "span": 6, "content": [ {"type": "field", "name": "amount", "properties": {"widget": "NumberInput"}} ] }, { "type": "column", "span": 6, "content": [ {"type": "field", "name": "date", "properties": {"widget": "DatePickerInput"}} ] } ] }, { "type": "footer", "content": [ { "type": "actionButton", "properties": { "label": "Confirm", "method": "action_confirm", "variant": "primary" } } ] } ]}Display Options
Section titled “Display Options”Control how the wizard opens with view-level properties:
| Property | Values | Default | Description |
|---|---|---|---|
size | xs, sm, md, lg, xl, fullscreen | Auto | Modal/drawer size |
displayMode | drawer, modal | Auto | How the wizard opens |
Default behavior (when not specified):
- Multi-step wizards (has
stepper): Fullscreen modal (xl) - Single-step wizards: Drawer (
md)
Custom Size Example
Section titled “Custom Size Example”{ "data_type": "UiView", "identifier": "large_wizard_view", "type": "Wizard", "model": "MyWizard", "size": "lg", "displayMode": "drawer", "arch": [...]}Multi-Step Wizard
Section titled “Multi-Step Wizard”Use a stepper element for multi-step wizards:
{ "data_type": "UiView", "identifier": "onboarding_wizard_view", "type": "Wizard", "model": "OnboardingWizard", "arch": [ { "type": "stepper", "steps": [ {"label": "Company Info", "key": "company"}, {"label": "Users", "key": "users"}, {"label": "Settings", "key": "settings"} ] }, { "type": "step", "key": "company", "content": [ {"type": "field", "name": "company_name"} ] }, { "type": "step", "key": "users", "content": [ {"type": "field", "name": "admin_email"} ] }, { "type": "step", "key": "settings", "content": [ {"type": "field", "name": "timezone"} ] }, { "type": "footer", "content": [ {"type": "actionButton", "properties": {"label": "Complete", "method": "action_confirm", "variant": "primary"}} ] } ]}Footer Buttons
Section titled “Footer Buttons”The footer element defines action buttons:
{ "type": "footer", "content": [ { "type": "actionButton", "properties": { "label": "Confirm", "method": "action_confirm", "variant": "primary" } }, { "type": "actionButton", "properties": { "label": "Save Draft", "method": "action_save_draft", "variant": "outline" } } ]}A Cancel button is automatically added to all wizards.
Window Actions
Section titled “Window Actions”Connect views to the application:
{ "data_type": "WindowAction", "identifier": "leads_action", "name": "Leads", "model": "CrmLead", "modes": "Kanban,List,Form", "default_view": "lead_kanban_view", "views": [ "lead_kanban_view", "lead_list_view", "lead_form_view" ], "search_view": "lead_search_view", "context": { "view": { "filters": ["my_leads"] } }}Action Properties
Section titled “Action Properties”| Property | Description |
|---|---|
name | Action display name |
model | Model to display |
modes | Available view modes |
default_view | Default view identifier |
views | List of view identifiers |
search_view | Search view identifier |
context | Default context |
limit | Records per page |
global_filter | Always-applied filter |
Context: field prefills vs. view directives
Section titled “Context: field prefills vs. view directives”The action context carries two distinct kinds of entry, kept in separate
namespaces so they can never be confused:
default_<field>— pre-fills<field>on a new record created from the action (e.g.default_company: 3,default_type: "Opportunity", or a relational command-form default likedefault_lines: [["C", {...}]]). The create path maps everydefault_<field>straight onto the model field of that name.view: { filters: [...], groups: [...] }— list/search view directives: which search-viewfilter/groupidentifiers start active. These describe the view, not a record, and are read only by the list/search layer.
Keeping view directives under view (rather than default_filters /
default_groups) is deliberate: their names reference data dimensions and would
otherwise collide with a same-named model field. For example User owns a filters
relation, so a default_filters key would be mis-read as a field prefill and inject
bogus relational command data on user creation. The view namespace makes that
collision structurally impossible — no reserved-key list to maintain.
Next Steps
Section titled “Next Steps”- Widgets - All available widgets
- View Inheritance - Extending views