Here is the model we’re using in the experimental app that @thomas.campbell1 presented at the user group:
from anvil.tables import app_tables
from anvil_reactive.main import reactive_class
from .helpers import LinkedClass, WithLinks, WithView
@reactive_class
class Book(
WithView, app_tables.book.Row, buffered=True, attrs=True, client_writable=True
):
table = app_tables.book
key = "isbn_13"
@reactive_class
class Author(
WithLinks,
WithView,
app_tables.author.Row,
buffered=True,
attrs=True,
client_writable=True,
):
table = app_tables.author
key = "name"
links = [LinkedClass(Book, column="author", on_delete="restrict")]
def __str__(self):
return self.name
That’s already starting to look a little unwieldy with just the two mixins that we’ve created thus far and it’s highly likely we’ll create more.
I’d like instead, to be able to create my own base class with all the various options set how I want in one place. Something like:
from anvil.tables import app_tables
from anvil_reactive.main import reactive_class
from .helpers import LinkedClass, WithLinks, WithView
@reactive_class
class MyBaseModel(
WithLinks,
WithView,
app_tables.Row, # <- This is where things might get tricky, I suspect
buffered=True,
attrs=True,
client_writable=True
):
pass
class Book(MyBaseModel):
table = app_tables.book
key = "isbn_13"
class Author(MyBaseModel):
table = app_tables.author
key = "name"
links = [LinkedClass(Book, column="author", on_delete="restrict")]
def __str__(self):
return self.name
stucork
February 28, 2025, 11:09am
2
Hmm - this could be a job for a metaclass
class MetaRow(type):
def __new__(cls, name, bases, namespace, **kwargs):
# adjust the bases here
return super().__new__(
cls, name, bases, namespace, attrs=True, client_writable=True, buffered=True
)
class Author(app_tables.author.Row, metaclass=MetaRow):
table = app_tables.author
key = "name"
links = [LinkedClass(Book, column="author", on_delete="restrict")]
def __str__(self):
return self.name
(and looks like we’ll need to fix the linting issue with setting a metaclass)
Ha! I’d thought the same thing but metaclasses are one of things that trigger “if you think this is the solution, there’s a very good chance you’ve missed something simpler” in my head.
1 Like
For what it’s worth, I share that intuition about metaclasses!
It seems like the issue here is specifically the config options (buffered=True
, attrs=True
, client_writable=True
), as the rest can be combined with inheritance. Just spitballing, no promises, but would something like this appeal?
class MyBaseModel(WithLinks, WithView):
_anvil_model_options_ = {"buffered":True, "attrs":True, "client_writable":True}
class Book(MyBaseModel, app_tables.books.Row):
# ...
1 Like
That would definitely solve 90% of the issue!
The remaining 10% is the irritation of having to specify the table twice but that’s a much bigger change, I suspect.
Definitely a “sit on your hands, go for a walk, don’t touch the damned keyboard” trigger!
Tell me more about why you’d still want to do this?
1 Like
We have a WithView
mixin because we’re using client readable views to populate “index” forms with the lists of existing instances.
To do that, we need to know the table within the model class.
Here’s the code:
params = {self.column: row}
results = self.klass.get_view().search(**params)
if len(results) > 0:
raise ChildExists("Child row found, cannot delete parent row")
def cascade_on_delete(self, row):
params = {self.column: row}
self.klass.get_view().search(**params).delete_all_rows()
@portable_class
class WithView:
table = None
@server_method
@classmethod
def get_view(cls):
return cls.table.client_readable()
@portable_class
2 Likes