Skip to content

How to define a "Has Many" reference between models

Please refer to here for general information about models.

If you define a "Has Many" reference between models, you are creating a Master/Detail relationship. You have to operate in both master and detail models:

  • Master model: define the DetailReferences node listing detail references
  • Detail model: define a reference field that points back to the master model.

Example from TasKitto:

yaml
# model PROJECT.yaml
ModelName: PROJECT
Fields:
  PROJECT_ID: String(32) not null primary key
    IsVisible: False
    DefaultValue: %COMPACT_GUID%
  PROJECT_NAME: String(40) not null
  CUSTOMER: Reference(CUSTOMER) not null
    Fields:
      CUSTOMER_ID:
  STATUS: String(12)
    AllowedValues:
      Offered: Offered
      Open: Open
      Stopped: Stopped
      Terminated: Terminated
DetailReferences:
  PHASE: PHASE

# model PHASE.yaml
ModelName: PHASE
Fields:
  PHASE_ID: String(32) not null primary key
    IsVisible: False
    DefaultValue: %COMPACT_GUID%
  PHASE_NAME: String(40) not null
  PROJECT: Reference(PROJECT) not null
    Fields:
      PROJECT_ID:
  START_DATE: Date
  END_DATE: Date
  STATUS: String(12)
    AllowedValues:
      Started: Started
      Finished: Finished
      Invoiced: Invoiced
      Killed: Killed
      Waiting: Waiting

Field names, column names and the order of the key

The Model above uses the same identifier for the field and for the column, which is the common case when the schema was designed for Kittox. It is not a requirement: a Model imported from an existing database usually looks like this, with beautified field names and a prefixed foreign key column in the detail.

yaml
# model Palazzi.yaml (master)
Fields:
  PalId: Integer not null primary key
    PhysicalName: PAL_ID

# model Uffici.yaml (detail)
Fields:
  UffPal: Reference(Palazzi) not null
    PhysicalName: UFF_PAL_FK
    Fields:
      UffPalId:
        PhysicalName: UFF_PAL_ID

Both work, and you do not need to rename the master key field to match its column.

Two rules are worth knowing when you write this metadata by hand:

  • The reference in the detail must carry the Fields: sub-node naming the local foreign key column. Reference(Palazzi) alone says which Model is pointed at, not which column holds the link, and a detail can legitimately point at the same master more than once.
  • Master and detail are paired by position: key field i of the master corresponds to sub-field i of the reference. With a single-column key there is nothing to get wrong; with a composite key, declare the sub-fields in the same order as the master's key fields, or the grid will show the rows of a different master record. The Model Wizard writes the right order, and keeps it aligned when the database changes: re-running Update models reorders both the master's key and the reference's sub-fields to follow the primary key and the foreign key as the database declares them.

The DetailReferences entry on the master carries no key information: it declares that the relationship is navigated as a grid, and the pairing is derived from the reference in the detail. ReferenceField: is only needed when the detail points at the master more than once.

Deleting the master

By default the master cannot be deleted while details refer to it: the database refuses on the foreign key, and the application reports it by naming the record and counting what holds it back, for instance Project "Conferest Kitto3" cannot be deleted: 2 Phases still refer to it.

To have the details go with it, say so on the relationship — as a subnode of the detail reference, one level in:

yaml
# model PROJECT.yaml (master)
DetailReferences:
  PHASE: PHASE
    CascadeDelete: True

The details are then deleted first, inside the master's own transaction, so either the whole delete happens or none of it does. It applies per relationship, so two details of the same master can behave differently, and it nests: a detail's own details go before it, deepest first.

It is off unless asked for. On by default, a single delete would take an unknown number of rows with it in every application without anyone having decided that.

Note that the cascade follows the detail tables the view declares. A relationship the view does not mention is not deleted, and the database refuses as before — with the readable message rather than a constraint name.

Rendering master/detail in views

To display the master/detail relationship in the UI, define the MainTable/DetailTables node in the view:

yaml
# view Projects.yaml
Type: Data
Controller: List

MainTable:
  Model: PROJECT
  DetailTables:
    Table:
      Model: PHASE
      Controller:
        Form:
          Layout: Phases_Form

When editing a master record, detail tables appear as additional tabs in the form dialog, merged into the same tab bar as any PageBreak tabs from the form layout. For example, if the Project form layout has no page breaks, the tab bar shows [Project | Phases].

The detail grid has its own CRUD toolbar (Add, Edit, Delete) and supports double-click to open the detail record form.

Auto-built vs. explicit views

By default, when you specify only Model: PHASE, Kittox auto-builds a list view with a standard grid, toolbar, and search filter. If you need more control over the detail's columns, filters, or toolbar, you can reference an explicit view:

yaml
  DetailTables:
    Table:
      ViewName: Phases

FK field handling

When a detail form is opened from the master, the foreign key field (e.g., PROJECT in a PHASE record) is automatically:

  • Read-only in all modes (edit, view, add) — the user cannot change the master link.
  • Pre-filled when adding a new record — set to the current master record's key.

Nested details

Detail tables can themselves have detail tables:

yaml
# view Customers.yaml
MainTable:
  Model: CUSTOMER
  DetailTables:
    Table:
      Model: PROJECT
      DetailTables:
        Table:
          Model: PHASE
          Controller:
            Form:
              Layout: Phases_Form
      Controller:
        PreventAdding: False
        PreventDeleting: False
        AllowDuplicating: True

In this example, editing a Customer shows a PROJECT tab; editing a Project shows a PHASE tab.

Custom form layout for details

You can specify a form layout for the detail record dialog:

yaml
  DetailTables:
    Table:
      Model: PHASE
      Controller:
        Form:
          Layout: Phases_Form

The layout file (e.g., Phases_Form.yaml under Metadata/Views/Layouts/) controls which fields appear and how they are arranged:

yaml
# Phases_Form.yaml
Row:
  Field: PROJECT
    CharWidth: 40
Field: PHASE_NAME
Row:
  Field: START_DATE
  Field: END_DATE
  Field: STATUS

See Form for full details on form layouts and the master/detail feature.

Released under Apache License, Version 2.0.