When youâre building an app with Data Tables, you sometimes want to attach logic, server functions, and validation to the data youâre storing. Spreading that code among forms, modules and server functions can get messy.
Today, weâre releasing several new features in Accelerated Tables to make database-backed apps with Anvil much cleaner:
-
Model Classes, where you can specify your own class to use for rows from a particular table.
-
Buffered Changes, where you can make temporary changes to a row, and either save them or roll them back. This means you can use data bindings with your model classes.
-
Client-writable models, where you can write code as though youâre writing to data tables directly from client Form code, but all changes must go via your model code (running on the server) before they can be applied.
Note 1: This is the first version of this functionality. Itâs fully documented and supported, but it will have some rough edges â and we want to hear your feedback about what is easy and hard with this new model. We look forward to your posts in Q&A or Show&Tell!
You can click on those links and jump right into the docs, or read on for a bit more detail about each of them.
Note 2: Massive props to @stucork, who implemented the lionâs share of this!
1. Model Classes
A model class is a subclass of the Data Tables Row object. If you define a model class for a particular table, Anvil will instantiate your class for every row of that table, instead of the generic Row object. You can add properties, methods, and even server-side methods (like server functions, but in your class). Anywhere that Anvil would give you a row object (search iterators, the get()
method, following links from other tables), it will give you a model object instead if one exists.
You can jump straight into the docs, but hereâs a simple example to get you started. It works with a people
table that has columns for name
and date_of_birth
:
class Person(app_tables.people.Row):
@property
def age(self):
days = (date.today() - self["date_of_birth"])).days
return days // 365
for person in app_tables.people.search():
print(f"{person['name']} is {person.age} years old")
You can do a lot more than this with Model Classes â check out the docs!
2. Buffered changes
We often want to make temporary changes to row objects, or create âdraftsâ of records that have not yet been saved in the database. Right now we usually do that with dictionaries, but they donât have all that model-class goodness! So now, you can buffer changes to a particular row.
For example, when youâre popping up an edit dialog, with âSaveâ and âCancelâ buttons, you can now do:
person = # ...get it from somewhere
with person.buffer_changes():
# the EditPerson form receives a Person instance, with
# all its properties and methods, not just a dict!
if alert(EditPerson(item=person),
buttons=[("Save", True), ("Cancel", False)]):
person.save() # otherwise changes are discarded
There are a few ways to invoke buffering â you can switch it on and off for individual rows, or specify that every instance of a model is always buffered.
Read all about it in the docs!
You can even create âdraft rowsâ, which are instances of the row/model class that you use before they are saved to the database. For example, to create a new Person record:
person = Person()
# Again, the EditPerson form receives a Person instance,
# with all its properties and methods, not just a dict!
if alert(EditPerson(item=person),
buttons=[("Save", True), ("Cancel", False)]):
person.save()
# now "person" is a real row saved in the database.
Of course, that snippet on its own would require your table to be client-writable, which has worrying security implications. But what if we hadâŚ
3. Client-writable models
The usual way to build web apps is to build a front-end UI to perform some operation, then build some back-end function to apply those changes to the database, then call the back-end from the front end. This is how we do things in Anvil, but it spreads your logic out across your app.
What if you could write a Model Class, specifying the rules for what changes are allowed to a particular piece of data, and then justâŚuse that model class everywhere? Look ma, no Server Modules!
Youâll definitely want to read the docs for this one, but hereâs a taste of what you can do. This snippet creates a model class that can be edited directly from client code, but only if youâre logged in, and timestamps every edit:
class Person(app_tables.people.Row,
client_updatable=True):
def _do_update(self, updates, from_client):
if not anvil.users.get_user():
raise Exception("You must be logged in")
updates['last_edited'] = datetime.now()
super()._do_update(updates, from_client)
Tell us what you think!
Weâre really excited to get this into your hands. Weâve developed these features by talking a lot to people who are already using Anvil for substantial database-backed applications, but the proof is in the pudding â we want to see how you use it! Weâre going to let it shake down a bit before we go rewriting our tutorials, but we think this is going to make database-backed apps much, much easier to write.
As ever, please ask questions and discuss these new features in the Q&A section, and show us what youâve built in Show&Tell!