Creating Model Classes
Model classes allow you to extend the Data Table Row class for a particular table.
To specify the model class to use for a particular Data Table:
-
Create a Module in your app to hold code that can be imported on both client and server
-
In that Module, define a class that inherits from
app_tables.[tablename].Row
(substituting[tablename]
with the table for which you are defining a model). -
Import that Module when your app starts up (eg from a startup Form) and from one of your Server Modules if you have them
Now every time your code interacts with that Data Table, you will receive instances of your model class instead of ordinary Row objects. Because the model class inherits from the Row class, it still supports the normal Data Tables Row API, so existing code that worked with rows from this table will continue to work.
For example, here is a model class for a To Do List app that has a property to check whether or not the task is overdue:
class TodoItem(app_tables.todo_items.Row):
@property
def is_overdue(self):
return datetime.now() > self["due_date"]
Options
You can specify additional options when inheriting from a row class:
-
buffered=True
: Automatically buffer changes to rows in this table, without having to call thebuffer_changes()
method on each object. -
attrs=True
: Allow access to column values as attributes on the object as well as by dictionary lookup (egrow.due_date
as well asrow["due_date"]
). -
client_updatable=True
,client_creatable=True
,client_deletable=True
,client_writable=True
: See Client-Writable Models.
Model classes and the server
Defining a model class in a Module means that your class can be imported both on the client and the server. However, sometimes you’ll want to define methods that can only run on the server or write code that is not visible to the client.
Server methods
If you want to write methods for your model class that will run on the server, you can use the @anvil.server.server_method
decorator. Methods with this decorator are callable on the client but are passed to the server to run. When server methods are defined in a Client Module, the method is still visible to the client even though it will never run there.
import anvil.server
class TodoItem(app_tables.todo_items.Row, buffered=True):
@anvil.server.server_method
def assign_to_on_call_user(self):
on_call_user = app_table.users.get(on_call=True)[0]
self["assigned_user"] = on_call_user
self
is trustworthy
You cannot set instance variables on a model. The only state allowed in a model is the values of the Data Table columns. Because of this, if you define server methods to perform privileged actions, self
is always trustworthy despite being passed from the client: it is guaranteed to be a row from the table corresponding to this model with every property derived directly from the database. (However, the other arguments to a server method come from untrusted client code, so should be treated with care.)
Server versions of models
Module code is visible on the client, so even though server methods run on the server, their code is visible to the client. If you want to create server methods with private implementations, you can define a server version of your model.
In a Server Module, create a class that inherits from your model class. Rows accessed on the server will now be instantiated as the server-side class, and rows accessed on the client will be the client-side class. Objects will be automatically converted between client-side and server-side versions when they are passed into or out of server code.
Now, in the client-side model, you can define a “stub” @server_method
that does nothing, and override it in the server-side model. The client will see only the stub method, but when it is called the server-side version will be executed.
class TodoItem(app_tables.todo_items.Row, buffered=True):
@anvil.server.server_method
@classmethod
def get_my_todos(cls):
# The actual implementation is hidden from the client
raise NotImplementedException
from . import Module1
class TodoItem(Module1.TodoItem):
@anvil.server.server_method(require_user=True)
@classmethod
def get_my_todos(cls):
me = anvil.users.get_user()
return app_tables.todos.search(assigned_user=me)
Example
Let’s put it all together in an example. We’re creating a To Do List application, which has a table called app_tables.todo_items
with a column called due_date
. We want to define a model class for it, so we create a Module and define the following class:
class TodoItem(app_tables.todo_items.Row, buffered=True):
@property
def is_overdue(self):
return datetime.now() > self["due_date"]
@server_method(require_user=True)
def assign_to_on_call_user(self):
on_call_user = app_table.users.get(on_call=True)[0]
self["assigned_user"] = on_call_user
@server_method
@classmethod
def get_my_todos(cls):
# The actual implementation is hidden from the client
raise NotImplementedException
We also create a Server Module, and define the following class:
from . import Module1
class TodoItem(Module1.TodoItem):
@server_method(require_user=True)
@classmethod
def get_my_todos(cls):
me = anvil.users.get_user()
return app_tables.todos.search(assigned_user=me)
Now, whenever we call app_tables.todo_items.search()
we get a sequence of TodoItem
instances (we’ll get the client-side version of TodoItem
on the client, and the server-side version if we’re running on the server). We can access the .is_overdue
property of these objects or call a server method with row.assign_to_on_call_user()
. Changes to these objects are buffered by default.
There is also a server-side classmethod whose implementation is hidden from the client: TodoItem.get_my_todos()
.
Do you still have questions?
Our Community Forum is full of helpful information and Anvil experts.