= S3Model = [[TOC]] See also: - [wiki:S3/S3Model/ComponentResources Component Resources] - [wiki:S3/S3Model/SuperEntities Super-Entities] == 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: {{{#!python 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: {{{#!python __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.}}} (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: {{{#!python 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: {{{#!python 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.}}} (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: {{{#!python class S3DeploymentModel(S3Model): ... def model(self): }}} To define a table, the model() function must use {{{self.define_table}}} (instead of {{{current.db.define_table}}}): {{{#!python 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): {{{#!python 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: {{{#!python 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: {{{#!python table = current.s3db.my_table }}} Loading a function: {{{#!python my_function = current.s3db.my_function }}} If you have the name in a variable, you can use the ''item''-notation instead: {{{#!python 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: {{{#!python # 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}}}: {{{#!python # 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: {{{#!python # 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.: {{{#!python # 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 [wiki:S3/S3Resource]): {{{#!python # 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