The Record base model, automatic timestamps and soft deletes, Meta options, serialisation with to_dict and to_json, and the get_or_none and get_or_create shortcuts.
from sillo.record import Modelfrom tortoise import fields
class Post(Model): id = fields.IntField(pk=True) title = fields.CharField(max_length=200) body = fields.TextField() author = fields.ForeignKeyField("models.User", related_name="posts")
class Meta: table = "posts" ordering = ["-created_at"]
def __str__(self): return self.titleModel is Tortoise’s Model with three mixins already applied. Fields,
relations, querysets and Meta all work as the
Tortoise documentation describes.
What you get for free
Section titled “What you get for free”Three fields are declared on the base class, so every model has them without saying so:
| Field | Behaviour |
|---|---|
created_at | Set to UTC now on insert. Never updated. |
updated_at | Set to UTC now on every save. |
deleted_at | Nullable. None means active. |
post = await Post.create(title="Hello", body="…")post.created_at # datetime, UTCpost.deleted_at # NoneThey are real columns and appear in your migrations. If you do not want them,
inherit from Tortoise’s Model directly. There is no way to switch them off
individually, because a base class that sometimes has a column is a base class
whose migrations are unpredictable.
__str__ is worth writing
Section titled “__str__ is worth writing”The admin panel uses it as a row’s default label, and so does
every debugging session. A model without one shows as Post object (4).
Serialisation
Section titled “Serialisation”post.to_dict()post.to_dict(exclude=["body"])post.to_dict(include=["id", "title"])
post.to_json()post.to_json(indent=2)include wins when both are given: it is a whitelist, and a whitelist that
also honoured a blacklist would be ambiguous about which one was the mistake.
Relations are not followed by default. Fetch them first:
await post.fetch_related("author")Fetch shortcuts
Section titled “Fetch shortcuts”post = await Post.get_or_none(id=4)None instead of raising DoesNotExist. The right shape when absence is an
expected answer, a lookup by a user-supplied id, say.
tag, created = await Post.get_or_create( slug="python", defaults={"title": "Python"},)Returns the instance and whether it was created. defaults supplies the fields
used only on creation; the rest are the lookup.
Soft deletes
Section titled “Soft deletes”await post.soft_delete() # sets deleted_atawait post.restore() # clears itawait post.delete() # actually deletes the row
await Post.active() # deleted_at IS NULLawait Post.deleted() # deleted_at IS NOT NULLawait Post.count_active()active() and deleted() return querysets, so they chain:
recent = await Post.active().order_by("-created_at").limit(10)More in Mixins.
Tortoise’s Meta options all apply: table, ordering, unique_together,
indexes, abstract, table_description.
class Meta: table = "posts" ordering = ["-created_at"] unique_together = (("author", "slug"),)The base class sets manager = RecordManager(), which is what applies
global scopes. If you set your own manager, subclass
RecordManager or global scopes stop being applied.
The rest
Section titled “The rest”- Fields: the field types Record adds
- Mass assignment:
fillableandguarded - Mixins: the composable behaviours
- Bulk operations:
bulk_create,upsert,bulk_upsert