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.
#Link to another record
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:
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:
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:
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.
#Copy values from a Link
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:
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:
{'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.