Docs

Links and children

Records point at each other in two ways. A Link points at a record that lives on its own, such as the customer on an invoice. A child table holds rows that belong to one record only, such as the lines of an invoice.

A Link stores the id of a record of another model. Name the target by its table name, the class name in lower case.

Here is a product, in apps/shop/models/product.py:

Python
from evlet import Currency, Model, Rule, Text


class Product(Model):
    permissions = {
        'admin': Rule(('read', 'create', 'update', 'delete')),
        'editor': Rule(('read',)),
    }
    product_name = Text(required=True, unique=True, max_length=120)
    price = Currency(currency='AED', default=0, minimum='0')

    class Meta:
        title_field = 'product_name'

And an invoice that links to a customer:

Python
customer = Link('customer', required=True)

What a Link gives you:

  • The form shows a search box that finds customers by their title_field.
  • A Link must point at a record that exists. You cannot delete a customer while an invoice still links to it. Evlet never deletes linked records for you.
  • The person saving must be allowed to read the record they link to.

From Python, related() follows a Link inside a transaction and returns the linked record, or None when the Link is empty.

#Child tables

Some rows belong to one record and nothing else. Invoice lines are the usual example. Declare the row as a Child class inside its document, and add a Table field:

Python
from evlet import Child, Currency, Date, Decimal, Link, Model, Table, Text
from evlet.calc import F, round


class Invoice(Model):
    class Line(Child):
        product = Link('product', required=True)
        description = Text(fetch='product.product_name')
        qty = Decimal(scale=2, default=1, minimum='0')
        rate = Currency(currency='AED', initial=F('product.price'))
        amount = Currency(currency='AED', compute=round(qty * rate, 2))

    customer = Link('customer', required=True)
    issued_on = Date()
    lines = Table(Line)

The full invoice, with its totals and states, is on the next pages. Rows in a child table:

  • have their own table in the database, called invoice_lines, and their own stable ids;
  • keep the order people put them in;
  • are saved, checked and permission-checked together with their document, in one transaction;
  • have no list page or API route of their own. You read and change them through the document;
  • are removed when the document is removed.

A table holds at most 100 rows per document.

Two field options read values from the linked record:

  • fetch='product.product_name' copies the product's name into the line. The copy stays editable. It is refreshed when someone picks another product.
  • initial=F('product.price') suggests the product's price as the line's rate. People can change it.

#Save a document with its rows

Send the rows with the document, under children:

Python
with app.business.context(user) as context:
    pen = context.create(Product, product_name='Pen', price='2.50')
    invoice = context.create(Invoice, customer=customer['id'],
                             children={'lines': [{'product': pen['id'], 'qty': '4'}]})

The saved line comes back with its id, the fetched description, the suggested rate and the worked-out amount:

Output
{'id': RecordId('01jrn6dysca0e'), 'product': RecordId('01jrn6dyjtpye'), 'description': 'Pen',
 'qty': '4.00', 'rate': '2.50', 'amount': '10.00'}

When you update a document, leave children out to keep its rows as they are. Send a full list of rows to replace them: rows with an id are kept and changed, new rows have no id, and saved rows you leave out are removed.

#Rows in a migration

Adding a child table to a model that already has data needs a migration with AddTable. See Migrations.