Super-Entities
Table of Contents
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):
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
Instance tables as well as shared components both 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.
Attachments (1)
-
super_entity.png
(38.8 KB
) - added by 11 years ago.
Super-Entity Relationships
Download all attachments as: .zip