wiki:S3/S3Model/SuperEntities

Version 35 (modified by Dominic König, 8 years ago) ( diff )

--

Super-Entities

Introduction

Sometimes it is useful to link the same component to multiple master entities without introducing a separate foreign key constraint for each master.

Example: both warehouses and offices have contact persons. Instead of creating separate warehouse_contact and office_contact components, you can make use of the super-entity concept to link a single site_contact component to both entities.

To achieve this, S3 supports a special type of key tables called Super-Entities (referring to the fact that they constitue a generalization concept):

Super-Entity Relationships

A super-entity stores (tablename, uuid) pairs which identify records across multiple tables, and the primary key of the super-entity (super-key) can then be used for foreign key constraints to link components. The primary key of the super-entity is usually referred to as super-key, and the respective foreign key constraints are called super-link.

The super-entity concept is implemented in S3Model, and supported by both S3CRUD and S3Import. Custom methods can make use of the current.s3db API to handle super-entities as described below.

API

To simplify the handling of super-entities in the model, S3Model provides a unified API for super-entities:

Defining a Super-Entity

A super-entity can be defined using the super_entity() method of S3Model:

class MyModel(S3Model):

    def model(self):

        # Define a dict with nice names for the instance types:
        my_instance_types = {
            "my_instance_tablename": current.T("Nice name for the instance type"),
        }

        # Define the super-entity
        tablename = "my_super_entity"
        table = self.super_entity(tablename, 
                                  "my_super_id",                   # specify the super ID
                                  my_instance_types,               # specify the instance types
                                  Field("start_date", "datetime"), # shared field
                                  location_id())                   # shared field

Shared fields will mirror the values from the respective instance record, which allows easy access without need to join multiple tables.

Defining an Instance of a Super-Entity

A table is declared an instance of a super-entity by configuring the super_entity hook (continuing the example from above):

        # Define the instance table
        tablename = "my_instance_table"
        table = self.define_table(tablename,
                                  # Instance table must have the super ID:
                                  self.super_link("my_super_id", "my_super_entity"),
                                  Field("start_date", "datetime"),
                                  Field("end_date", "datetime"),
                                  *s3_meta_fields())

        # Configure the instance table
        self.configure("my_instance_table",

                       # Configure the super entity
                       super_entity = "my_super_entity",
                      )

By default, all fields which have the same name in super-entity and instance table will become shared fields (see Defining a Super-Entity).

If only a subset of these fields shall be shared, or the shared fields have different names in super entity and instance table, you can define a field mapping like this:

        # Define the instance table
        tablename = "my_instance_table"
        table = self.define_table(tablename,
                                  # Instance table must have the super ID:
                                  self.super_link("my_super_id", "my_super_entity"),
                                  Field("first_date", "datetime"),
                                  Field("last_date", "datetime"),
                                  *s3_meta_fields())

        # Configure the instance table
        self.configure("my_instance_table",

                       # Configure the super entity
                       super_entity = "my_super_entity",

                       # Map the "first_date" field in my_instance_table to "start_date" in my_super_entity
                       my_super_entity_fields = {"start_date": "first_date"},
                  )

Linking to a Super-Entity

Both, instance tables as well as shared components need to be linked to the super-entity.

This can be done by inserting a super-key field into the instance/component table which must have the same name as the primary key of the super-entity. You can use the super_link() function to do this:

tablename = "my_example"
define_table(tablename,
             super_link("pe_id", "pr_pentity"),
             super_link("sit_id", "sit_situation"),
             ...)

Note: super_link generates a Field instance - if you just need the name of the super-key field, you can use:

sk = s3db.super_key(db.pr_pentity) # returns the string "pe_id"

...or just type the field name (best option in models with lazy tables).

super_link() allows overriding a number of field settings:

tablename = "my_example"
define_table(tablename,
             super_link("pe_id", "pr_pentity"),
             super_link("sit_id", "sit_situation",
                        label = ...,                # field label for CRUD forms
                        comment = ...,              # field comment for CRUD forms
                        represent = ...,            # representation method for field values
                        orderby = ...,              # orderby-expression for IS_ONE_OF
                        sort = ...,                 # alpha-sort options in IS_ONE_OF
                        filterby = ...,             # inclusive filter for IS_ONE_OF by field
                        filter_opts = ...,          # options for filter_by
                        not_filterby = ...,         # exclusive filter for IS_ONE_OF by field
                        not_filter_opts = ...,      # options for not_filterby
                        instance_types = ...,       # limit selection to certain instance types
                        realms = ...,               # limit selection to realms
                        updateable = ...,           # limit selection to updateable instances
                        groupby = ...,              # groupby for IS_ONE_OF
                        script = ...,               # script for selector widget
                        widget = ...,               # selector widget
                        empty = ...,                # allow field to be None
                        default = ...,              # default value for the field
                        ondelete = "CASCADE",       # ondelete option for the field
                        readable = False,           # field is readable in CRUD forms
                        writable = False,           # field is writable in CRUD forms
                        ),
             ...)

Updating a Super-Entity

If you use s3_rest_controller() for CRUD, it will automatically create, update and delete super-entities as necessary.

Otherwise, or if you manipulate records outside s3_rest_controller, you have to update all super-entities which an instance table implements whenever you create, update or delete a record in that table.

To do this, you can use:

s3db.update_super(table, record)

where table is the instance table and record the newly created/updated record.

In case you're going to delete a record from an instance table, you may use:

s3db.delete_super(table, record)

before you delete the record, where table is the instance table and record the record to be deleted.


DeveloperGuidelines

Attachments (1)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.