wiki:S3/S3Model

Version 13 (modified by Dominic König, 7 years ago) ( diff )

--

S3Model

See also:

Purpose

The S3Model class defines the framework for data models in Eden. It extends the web2py concept by:

  • implicit and lazy model loading
  • an extensible, per-table configuration pattern
  • a meta-model for projected entities (the so-called "resource component model")
  • a meta-model for multi-table keys (the so-called "super-entity model")

Data models can be implemented as subclasses of S3Model, and then loaded on demand - with automatic resolution of cross-model dependencies. This saves processing time as it will always only load those models which are actually needed to process the request.

Defining Models

Modules

S3 data models reside in modules/s3db.

The file name of each Python module in modules/s3db corresponds to the Eden module prefix. All names with this prefix will be looked up from this file.

Example:

  • Tablename: org_office => Module Prefix: "org" => Looked up from: modules/s3db/org.py

All S3 data models need to be imported in models/00_tables.py:

import s3db.assess
import s3db.asset
import s3db.auth
import s3db.cap
...

Every Python module in modules/s3db must have an __all__ statement declaring the classes and functions to be imported:

__all__ = ["S3DeploymentModel",
           "S3DeploymentAlertModel",
           "deploy_rheader",
           "deploy_apply",
           "deploy_alert_select_recipients",
           "deploy_response_select_mission",
           ]

Important: Undeclared classes or functions are not available to the model loader!

All names in __all__ starting with the module prefix (e.g. deploy_) can be accessed globally with current.s3db.<name> (e.g. current.s3db.deploy_apply), without need to import them explicitly.

The modules/s3db/skeleton.py module is a well-commented skeleton module to explain how things should look like inside an S3 model.

Model Classes

Every data model is defined as a subclass of S3Model:

class S3DeploymentModel(S3Model):

Names

All names from the model which shall be globally accessible (i.e. tables, functions, variables, classes) must be declared in the names-variable:

class S3DeploymentModel(S3Model):

    names = ["deploy_event_type",
             "deploy_mission",
             "deploy_mission_id",
             "deploy_mission_document",
             "deploy_application",
             "deploy_assignment",
             "deploy_assignment_appraisal",
             "deploy_assignment_experience",
             ]

These names can then be accessed via current.s3db.<name> (e.g. current.s3db.deploy_mission_id).

Important: All table names and names which are returned from a model class must use the module prefix (otherwise they can't be found).

model() function

Every S3Model subclass must implement the model() function. This function defines all tables, functions and variables of the model:

class S3DeploymentModel(S3Model):

    ...

    def model(self):

To define a table, the model() function must use self.define_table (instead of current.db.define_table):

    def model(self):

        tablename = "deploy_mission"
        table = self.define_table(tablename,
                                  ...)

The model function must return a dict with the definitions of all names as declared in the names class-variable (except table names):

class MyModel(S3Model):

    names = ["my_function", "my_variable"]

    def model(self):

        my_variable = "example"

        return dict(my_own_function = self.my_function
                    my_variable = my_variable
                   )

    @staticmethod
    def my function():

      ...
      return

Ideally, custom functions in model classes which are returned from model() should be declared @staticmethod or @classmethod to allow the instance to be garbage-collected (i.e. release the thread-global pointer to the instance from current.s3db).

defaults() function

Every model class should define a defaults() function which returns safe defaults for the declared names in case the Eden module has been disabled per deployment-settings.

This is particularly important for re-usable fields holding foreign keys to tables defined in this model:

class S3DeploymentModel(S3Model):

    names = [...
             "deploy_mission_id",
             ]

    def model(self):

        ...

        mission_id = S3ReusableField("mission_id", table,
                                     ...
                                     )

        return dict(deploy_mission_id = mission_id)

    def defaults(self):

        # Module disabled, define a safe default for "mission_id":
        mission_id = S3ReusableField("mission_id", "integer", readable=False, writable=False)
        return dict(deploy_mission_id = mission_id)

Utility functions

The S3Model base class implements a number of useful helper functions to implement models, among others:

  • super_entity and super_link to define or reference super entities
  • add_component to define resource components
  • configure to define table configuration settings

These functions should not be overwritten in the subclass.

current.s3db

current.s3db is a global, empty instance of the S3Model class that loads tables, functions and variables from other models on demand.

Loading Tables, Functions and Variables from Models

current.s3db allows easy access to names using the attribute-notation:

Loading a table:

table = current.s3db.my_table

Loading a function:

my_function = current.s3db.my_function

If you have the name in a variable, you can use the item-notation instead:

tablename = "my_table"
table = current.s3db[tablename]

Note: The attribute- or item-notations will raise an AttributeError if the name can not be found (e.g. when the respective module is disabled).

To avoid exceptions due to disabled modules, one can use the table() function to access tables:

# Returns None if "my_table" is not found
table = current.s3db.table("my_table")

Note: s3db.table() will also return functions or variables with the specified name.

To limit the lookup to tables, use db_only:

# Returns only tables, but not functions or variables
table = current.s3db.table("my_table", db_only=True)

To only lookup functions and variables, but not tables, you can use the get() function:

# Returns only functions or variables, but not tables
my_function = current.s3db.get("my_function")

Utility Functions

current.s3db also provides the S3Model utility functions, e.g.:

# Change a table configuration
current.s3db.configure("my_table", list_fields=["id", "name", "other_field"])

Note:: s3db.get_config() does not load the respective model, so unless you have loaded the model before, you can not access its configuration settings!

Constructing Resources

current.s3db also provides a method to define a resource (see S3/S3Resource):

# Define a resource
resource = current.s3db.resource("my_table")

# Limit to certain record IDs
resource = current.s3db.resource("my_table", id=[1, 3, 7])

# Use a filter
from s3 import S3FieldSelector
resource = current.s3db.resource("my_table", filter=S3FieldSelector("id").belongs([1, 3, 7]))

# Apply a URL filter
resource = current.s3db.resource("my_table", vars={"~.id__belongs": "1, 3, 7"})

Concepts

Resources

- tbw

Resource Components

- tbw

Resource Configuration

- tbw

Resource Methods

- tbw

Super-Entities

- tbw


DeveloperGuidelines

Note: See TracWiki for help on using the wiki.