Skip to content

Worked example: build a Helpdesk

This tutorial builds a complete Helpdesk app from an empty folder. When you finish you'll have a ticket model, a list / form / kanban / search interface, a menu, and some demo data — all without writing any frontend code.

It assumes you've skimmed How modules work. Every construct used here is explained in the Developer Guide; this page ties them together.

What you'll build

A Helpdesk app with a Ticket model: a subject, a description, a priority, a stage, and a link to the customer (a contact). Users get a table, a card board grouped by stage, a form, and filters.

Final layout:

helpdesk/
├── module.yaml
├── __init__.py
├── models/
│   ├── __init__.py
│   └── ticket.py
├── views/
│   └── helpdesk.xml
└── data/
    └── demo.xml

Step 1 — the manifest

Create helpdesk/module.yaml. It declares the module and lists which files to load. The Helpdesk depends on base (core menus) and contact (the contact model a ticket links to).

name: helpdesk
label: Helpdesk
version: 1.0.0
author: Your Company
requires: [base, contact]
models: [models]
data:
  - views/helpdesk.xml
  - data/demo.xml

Add an empty helpdesk/__init__.py so the folder is a Python package.

data order matters within a file's references

Files load top to bottom. Load your views before demo data that relies on those models.

Step 2 — the model

A model is a Python class that subclasses models.Model. Create helpdesk/models/ticket.py:

from victor import fields, models


class Ticket(models.Model):
    name = "ticket"                             # technical name (snake_case, singular)
    label = "Ticket"                            # human-readable name
    _description = "A customer support request."  # required on every model

    display_name = fields.String(label="Subject", required=True)
    description = fields.Text(label="Description")
    priority = fields.Selection(
        options=[("low", "Low"), ("normal", "Normal"), ("high", "High"), ("urgent", "Urgent")],
        label="Priority", default="normal",
    )
    stage = fields.Selection(
        options=[("new", "New"), ("in_progress", "In progress"), ("done", "Done")],
        label="Stage", default="new",
    )
    customer_id = fields.Reference(target="contact", label="Customer")
    resolved = fields.Boolean(label="Resolved", default=False)

Then register it from helpdesk/models/__init__.py:

from . import ticket  # noqa: F401

Why display_name?

The framework uses a record's display_name as its title — it's what a Reference picker shows when someone links a ticket elsewhere. Don't call a field name; that attribute is the model's technical name. See Models & fields.

Step 3 — the views

Create helpdesk/views/helpdesk.xml. One file declares every screen, plus the action and menu that make the app reachable.

<victor>
  <!-- Table -->
  <view id="view_list" model="ticket" type="list">
    <field name="display_name"/>
    <field name="priority"/>
    <field name="stage"/>
    <field name="customer_id"/>
    <field name="resolved"/>
  </view>

  <!-- Form -->
  <view id="view_form" model="ticket" type="form">
    <group string="Ticket">
      <field name="display_name"/>
      <field name="priority"/>
      <field name="stage"/>
      <field name="customer_id"/>
      <field name="resolved"/>
    </group>
    <notebook>
      <page string="Description">
        <field name="description" widget="textarea"/>
      </page>
    </notebook>
  </view>

  <!-- Card board grouped by stage -->
  <view id="view_kanban" model="ticket" type="kanban">
    <kanban default_group_by="stage" records_draggable="1">
      <field name="display_name"/>
      <field name="priority"/>
      <field name="customer_id"/>
    </kanban>
  </view>

  <!-- Filter bar -->
  <view id="view_search" model="ticket" type="search">
    <search>
      <filter name="open" string="Open" domain='[["resolved", "=", false]]'/>
      <filter name="high" string="High priority" domain='[["priority", "in", ["high", "urgent"]]]'/>
      <filter name="by_stage" string="Stage" context="{'group_by': 'stage'}"/>
    </search>
  </view>

  <!-- Bind the model to its screens, and add a menu -->
  <action id="action_tickets" name="Tickets" model="ticket"
          views="list,kanban,form" path="tickets"/>
  <menu id="menu_helpdesk" parent="base.menu_root" label="Helpdesk"
        action="helpdesk.action_tickets" icon="LifeBuoy" sequence="20"/>
</victor>

What this gives you:

  • views="list,kanban,form" makes list the landing screen, with a switcher to the kanban board; the form opens when a user clicks a ticket.
  • Dragging a card between kanban columns moves the ticket to that stage.
  • The search view adds Open and High priority chips and a group by stage option.

See Views and Actions & menus for the full DSL.

Step 4 — demo data

Create helpdesk/data/demo.xml. Seeds are created when the module installs. Here we create a customer and two tickets that link to it.

<victor>
  <seed model="contact" id="acme">
    <display_name>Acme Corp</display_name>
    <is_company>true</is_company>
  </seed>

  <seed model="ticket" id="t_login">
    <display_name>Cannot log in</display_name>
    <priority>high</priority>
    <stage>new</stage>
    <customer_id>acme</customer_id>
  </seed>

  <seed model="ticket" id="t_invoice">
    <display_name>Wrong amount on invoice</display_name>
    <priority>normal</priority>
    <stage>in_progress</stage>
    <customer_id>acme</customer_id>
  </seed>
</victor>

References resolve within the same file

<customer_id>acme</customer_id> works because the acme contact is a seed declared above in this same file. A reference to a record in another file or module is not resolved. See Seed & demo data.

Step 5 — install it

  1. Put the helpdesk/ folder in your project's git repository.
  2. Push. Victor Cloud deploys your module to the instance.
  3. In the app, open the top-level Modules section, find Helpdesk, and click Install.

Open the app menu — Helpdesk now appears. You'll land on the ticket list, with the two demo tickets and the kanban board grouped by stage. Full details in Publishing & installing.

Iterating

After changing your module, push again and click Upgrade on Helpdesk in the Modules section to apply the new version.

Step 6 — extend a neighbouring app (optional)

Modules compose. Suppose you want an Open tickets count on the contact form. You don't edit the contact app — you extend it from Helpdesk.

Add a field to the existing contact model by declaring a class with the same name (helpdesk/models/contact_ext.py, then import it from models/__init__.py):

from victor import fields, models


class ContactExt(models.Model):
    name = "contact"                      # extend the existing model
    open_tickets = fields.Integer(label="Open tickets")

Then inject it into the contact form with <extend> (add this to views/helpdesk.xml):

<extend id="contact_form_helpdesk" view="contact.view_form">
  <after field="email">
    <field name="open_tickets"/>
  </after>
</extend>

An Integer renders as a number by default — no widget needed. See Extending other modules.

Where to go next

Your Helpdesk is a normal Victor app now — add intelligence and automation to it:

Keep the Cheat sheet handy for the exact field types, widgets, and keys.