def model(self): T = current.T db = current.db settings = current.deployment_settings s3_datetime_format = settings.get_L10n_datetime_format() s3_utc_represent = lambda dt: S3DateTime.datetime_represent(dt, utc=True) add_component = self.add_component configure = self.configure crud_strings = current.response.s3.crud_strings define_table = self.define_table NONE = current.messages["NONE"] # --------------------------------------------------------------------- # Events # # Events are a way of grouping related Incidents # # --------------------------------------------------------------------- tablename = "event_event" table = define_table(tablename, Field("name", notnull=True, # Name could be a code length=64, # Mayon compatiblity label=T("Name")), Field("exercise", "boolean", represent = lambda opt: "√" if opt else NONE, #comment = DIV(_class="tooltip", # _title="%s|%s" % (T("Exercise"), # Should! # T("Exercises mean all screens have a watermark & all notifications have a prefix."))), label=T("Exercise?")), Field("zero_hour", "datetime", default = current.request.utcnow, requires = IS_DATETIME(format=s3_datetime_format), represent = s3_utc_represent, comment = DIV(_class="tooltip", _title="%s|%s" % (T("Zero Hour"), T("The time at which the Event started."))), label=T("Zero Hour")), Field("closed", "boolean", default = False, represent = s3_yes_no_represent, label=T("Closed")), s3_comments(), *s3_meta_fields()) # CRUD strings ADD_EVENT = T("New Event") crud_strings[tablename] = Storage( title_create = ADD_EVENT, title_display = T("Event Details"), title_list = T("Events"), title_update = T("Edit Event"), title_search = T("Search Events"), subtitle_create = T("Add New Event"), label_list_button = T("List Events"), label_create_button = ADD_EVENT, label_delete_button = T("Delete Event"), msg_record_created = T("Event added"), msg_record_modified = T("Event updated"), msg_record_deleted = T("Event deleted"), msg_list_empty = T("No Events currently registered")) represent = S3Represent(lookup=tablename) event_id = S3ReusableField("event_id", table, sortby="name", requires = IS_NULL_OR( IS_ONE_OF(db, "event_event.id", represent, filterby="closed", filter_opts=[False], orderby="event_event.name", sort=True)), represent = represent, label = T("Event"), ondelete = "CASCADE", # Uncomment these to use an Autocomplete & not a Dropdown #widget = S3AutocompleteWidget() #comment = DIV(_class="tooltip", # _title="%s|%s" % (T("Event"), # T("Enter some characters to bring up a list of possible matches"))) ) configure(tablename, orderby=~table.zero_hour, list_orderby=~table.zero_hour, update_onaccept=self.event_update_onaccept, deduplicate=self.event_duplicate, list_fields = ["id", "name", (T("Location"), "location.name"), "zero_hour", "exercise", "closed", "comments", ]) # Components # Incidents add_component("event_incident", event_event="event_id") # Locations add_component("gis_location", event_event=Storage(link="event_event_location", joinby="event_id", key="location_id", actuate="hide")) # Requests add_component("req_req", event_event="event_id") # Tags add_component("event_event_tag", event_event=dict(joinby="event_id", name="tag")) # --------------------------------------------------------------------- # Event Locations (link table) # tablename = "event_event_location" table = define_table(tablename, event_id(), self.gis_location_id( widget = S3LocationAutocompleteWidget(), requires = IS_LOCATION(), represent = self.gis_location_lx_represent, comment = S3AddResourceLink(c="gis", f="location", label = T("Add Location"), title=T("Location"), tooltip=T("Enter some characters to bring up a list of possible matches")), ), *s3_meta_fields()) # --------------------------------------------------------------------- # Event Tags # - Key-Value extensions # - can be used to identify a Source # - can be used to add extra attributes (e.g. Area, Population) # - can link Events to other Systems, such as: # * GLIDE (http://glidenumber.net/glide/public/about.jsp) # * Mayon # * WebEOC # - can be a Triple Store for Semantic Web support # tablename = "event_event_tag" table = define_table(tablename, event_id(), # key is a reserved word in MySQL Field("tag", label=T("Key")), Field("value", label=T("Value")), s3_comments(), *s3_meta_fields()) configure(tablename, deduplicate=self.event_event_tag_deduplicate) # --------------------------------------------------------------------- # Incidents # # Incidents are the unit at which responses are managed. # They can be Exercises or real Incidents. # They can be instantiated from Scenario Templates. # tablename = "event_incident" table = define_table(tablename, event_id(), self.event_incident_type_id(), self.scenario_scenario_id(), Field("name", notnull=True, # Name could be a code length=64, label=T("Name")), Field("exercise", "boolean", represent = lambda opt: "√" if opt else NONE, #comment = DIV(_class="tooltip", # _title="%s|%s" % (T("Exercise"), # Should! # T("Exercises mean all screens have a watermark & all notifications have a prefix."))), label=T("Exercise?")), Field("zero_hour", "datetime", default = current.request.utcnow, requires = IS_DATETIME(format=s3_datetime_format), represent = s3_utc_represent, comment = DIV(_class="tooltip", _title="%s|%s" % (T("Zero Hour"), T("The time at which the Event started."))), label=T("Zero Hour")), Field("closed", "boolean", default = False, represent = s3_yes_no_represent, label=T("Closed")), s3_comments(), *s3_meta_fields()) crud_strings[tablename] = Storage( title_create = T("Add Incident"), title_display = T("Incident Details"), title_list = T("Incidents"), title_update = T("Edit Incident"), title_search = T("Search Incidents"), subtitle_create = T("Add New Incident"), label_list_button = T("List Incidents"), label_create_button = T("Add Incident"), label_delete_button = T("Remove Incident from this event"), msg_record_created = T("Incident added"), msg_record_modified = T("Incident updated"), msg_record_deleted = T("Incident removed"), msg_list_empty = T("No Incidents currently registered in this event")) represent = S3Represent(lookup=tablename) incident_id = S3ReusableField("incident_id", table, sortby="name", requires = IS_NULL_OR( IS_ONE_OF(db, "event_incident.id", represent, filterby="closed", filter_opts=[False], orderby="event_incident.name", sort=True)), represent = represent, label = T("Incident"), ondelete = "RESTRICT", # Uncomment these to use an Autocomplete & not a Dropdown #widget = S3AutocompleteWidget() #comment = DIV(_class="tooltip", # _title="%s|%s" % (T("Incident"), # T("Enter some characters to bring up a list of possible matches"))) ) if settings.has_module("project"): create_next_url = URL(args=["[id]", "task"]) elif settings.has_module("hrm"): create_next_url = URL(args=["[id]", "human_resource"]) elif settings.has_module("asset"): create_next_url = URL(args=["[id]", "asset"]) else: create_next_url = URL(args=["[id]", "site"]) configure(tablename, create_next = create_next_url, create_onaccept=self.incident_create_onaccept, deduplicate=self.incident_duplicate, list_fields = ["id", "name", "incident_type_id", "exercise", "closed", "comments", ]) # Components # Tasks add_component("project_task", event_incident=Storage(link="event_task", joinby="incident_id", key="task_id", # @ToDo: Widget to handle embedded LocationSelector #actuate="embed", actuate="link", autocomplete="name", autodelete=False)) # Human Resources add_component("event_human_resource", event_event="event_id") add_component("hrm_human_resource", event_incident=Storage(link="event_human_resource", joinby="incident_id", key="human_resource_id", # @ToDo: Widget to handle embedded AddPersonWidget #actuate="embed", actuate="link", autocomplete="name", autodelete=False)) # Assets add_component("asset_asset", event_incident=Storage(link="event_asset", joinby="incident_id", key="asset_id", actuate="embed", autocomplete="name", autodelete=False)) # Facilities add_component("event_site", event_incident="incident_id") # Map Config add_component("gis_config", event_incident=Storage(link="event_config", joinby="incident_id", multiple=False, key="config_id", actuate="replace", autocomplete="name", autodelete=True)) # --------------------------------------------------------------------- # Pass names back to global scope (s3.*) # return Storage( event_event_id = event_id, event_incident_id = incident_id, )
def model(self): T = current.T db = current.db auth = current.auth person_id = self.pr_person_id location_id = self.gis_location_id organisation_id = self.org_organisation_id UNKNOWN_OPT = current.messages.UNKNOWN_OPT vehicle = current.deployment_settings.has_module("vehicle") # Shortcuts add_component = self.add_component configure = self.configure crud_strings = current.response.s3.crud_strings define_table = self.define_table super_link = self.super_link #-------------------------------------------------------------------------- # Assets # asset_type_opts = { ASSET_TYPE_VEHICLE: T("Vehicle"), #ASSET_TYPE_RADIO : T("Radio"), #ASSET_TYPE_TELEPHONE : T("Telephone"), ASSET_TYPE_OTHER: T("Other") } asset_item_represent = lambda id: self.supply_item_represent( id, show_um=False) ctable = self.supply_item_category itable = self.supply_item tablename = "asset_asset" table = define_table(tablename, super_link("track_id", "sit_trackable"), super_link("doc_id", "doc_entity"), Field("number", label = T("Asset Number")), self.supply_item_entity_id, # @ToDo: Can we set this automatically based on Item Category? Field("type", "integer", readable = vehicle, writable = vehicle, requires = IS_IN_SET(asset_type_opts), default = ASSET_TYPE_OTHER, represent = lambda opt: \ asset_type_opts.get(opt, UNKNOWN_OPT), label = T("Type")), self.supply_item_id(represent = asset_item_represent, requires = IS_ONE_OF(db((ctable.can_be_asset == True) & \ (itable.item_category_id == ctable.id)), "supply_item.id", asset_item_represent, sort=True, ), script = None, # No Item Pack Filter ), # This is a component, so needs to be a super_link # - can't override field name, ondelete or requires super_link("site_id", "org_site", label = T("Office/Warehouse/Facility"), default = auth.user.site_id if auth.is_logged_in() else None, readable = True, writable = True, empty = False, ondelete = "RESTRICT", # Comment these to use a Dropdown & not an Autocomplete #widget = S3SiteAutocompleteWidget(), #comment = DIV(_class="tooltip", # _title="%s|%s" % (T("Warehouse"), # T("Enter some characters to bring up a list of possible matches"))), represent = self.org_site_represent ), organisation_id(), Field("sn", label = T("Serial Number")), # @ToDo: Switch to using org_organisation filtered to suppliers #supplier_id(), Field("supplier", label = T("Supplier")), s3_date("purchase_date", label = T("Purchase Date") ), Field("purchase_price", "double", #default=0.00, represent=lambda v, row=None: IS_FLOAT_AMOUNT.represent(v, precision=2)), s3_currency("purchase_currency"), # Base Location, which should always be a Site & set via Log location_id(readable=False, writable=False), # Populated onaccept of the log to make a component tab person_id("assigned_to_id", readable=False, writable=False, comment=self.pr_person_comment(child="assigned_to_id")), s3_comments(), *(s3_address_fields() + s3_meta_fields())) # CRUD strings ADD_ASSET = T("Add Asset") crud_strings[tablename] = Storage( title_create=ADD_ASSET, title_display=T("Asset Details"), title_list=T("Assets"), title_update=T("Edit Asset"), title_search=T("Search Assets"), title_upload=T("Import Assets"), subtitle_create=T("Add New Asset"), label_list_button=T("List Assets"), label_create_button=ADD_ASSET, label_delete_button=T("Delete Asset"), msg_record_created=T("Asset added"), msg_record_modified=T("Asset updated"), msg_record_deleted=T("Asset deleted"), msg_list_empty=T("No Assets currently registered")) # Reusable Field asset_id = S3ReusableField( "asset_id", table, sortby="number", requires=IS_NULL_OR( IS_ONE_OF(db, "asset_asset.id", self.asset_represent, sort=True)), represent=self.asset_represent, label=T("Asset"), comment=S3AddResourceLink( c="asset", f="asset", tooltip= T("If you don't see the asset in the list, you can add a new one by clicking link 'Add Asset'." )), ondelete="CASCADE") table.virtualfields.append(AssetVirtualFields()) # Search Method asset_search = S3Search( # Advanced Search only advanced=( S3SearchSimpleWidget( name="asset_search_text", label=T("Search"), comment= T("You can search by asset number, item description or comments. You may use % as wildcard. Press 'Search' without input to list all assets." ), field=[ "number", "item_id$name", #"item_id$category_id$name", "comments", ]), S3SearchOptionsWidget(name="asset_search_L1", field="L1", location_level="L1", cols=3), S3SearchOptionsWidget(name="asset_search_L2", field="L2", location_level="L2", cols=3), S3SearchLocationWidget( name="asset_search_map", label=T("Map"), ), S3SearchOptionsWidget(name="asset_search_item_category", field="item_id$item_category_id", label=T("Category"), cols=3), )) report_fields = [ "number", (T("Category"), "item_id$item_category_id"), (T("Item"), "item_id"), (T("Office/Warehouse/Facility"), "site"), "L1", "L2", ] # Resource Configuration configure( tablename, super_entity=("supply_item_entity", "sit_trackable"), create_next=URL(c="asset", f="asset", args=["[id]"]), onaccept=self.asset_onaccept, search_method=asset_search, report_options=Storage(search=[ S3SearchOptionsWidget(name="asset_search_L1", field="L1", location_level="L1", cols=3), S3SearchOptionsWidget(name="asset_search_L2", field="L2", location_level="L2", cols=3), S3SearchOptionsWidget(name="asset_search_item_category", field="item_id$item_category_id", label=T("Category"), cols=3), ], rows=report_fields, cols=report_fields, facts=report_fields, methods=["count", "list"], defaults=Storage( aggregate="count", cols="L1", fact="number", rows="item_id$item_category_id")), list_fields=[ "id", "number", "item_id$item_category_id", "item_id", "type", "purchase_date", "organisation_id", "site_id", #"location_id", "L0", "L1", #"L2", #"L3", "comments", ]) # Log as component of Assets add_component("asset_log", asset_asset="asset_id") # Vehicles as component of Assets add_component("vehicle_vehicle", asset_asset=dict(joinby="asset_id", multiple=False)) # GPS as a component of Assets add_component("vehicle_gps", asset_asset="asset_id") # ===================================================================== # Asset Log # asset_log_status_opts = { ASSET_LOG_SET_BASE: T("Base Office/Warehouse/Facility Set"), ASSET_LOG_ASSIGN: T("Assigned"), ASSET_LOG_RETURN: T("Returned"), ASSET_LOG_CHECK: T("Checked"), ASSET_LOG_REPAIR: T("Repaired"), ASSET_LOG_DONATED: T("Donated"), ASSET_LOG_LOST: T("Lost"), ASSET_LOG_STOLEN: T("Stolen"), ASSET_LOG_DESTROY: T("Destroyed"), } #site_or_location_opts = {SITE :T("Site"), # LOCATION :T("Location")} asset_condition_opts = { 1: T("Good Condition"), 2: T("Minor Damage"), 3: T("Major Damage"), 4: T("Un-Repairable"), 5: T("Needs Maintenance"), } tablename = "asset_log" table = define_table(tablename, asset_id(), Field("status", "integer", label = T("Status"), requires = IS_IN_SET(asset_log_status_opts), represent = lambda opt: \ asset_log_status_opts.get(opt, UNKNOWN_OPT) ), s3_datetime("datetime", default="now", empty=False, represent="date", ), s3_datetime("datetime_until", label = T("Date Until"), represent="date", ), person_id(label = T("Assigned To")), Field("check_in_to_person", "boolean", #label = T("Mobile"), # Relabel? label = T("Track with this Person?"), comment = DIV(_class="tooltip", #_title="%s|%s" % (T("Mobile"), _title="%s|%s" % (T("Track with this Person?"), T("If selected, then this Asset's Location will be updated whenever the Person's Location is updated."))), readable = False, writable = False), # The Organisation to whom the loan is made organisation_id( readable = False, writable = False, widget = None ), #Field("site_or_location", # "integer", # requires = IS_NULL_OR(IS_IN_SET(site_or_location_opts)), # represent = lambda opt: \ # site_or_location_opts.get(opt, UNKNOWN_OPT), # widget = RadioWidget.widget, # label = T("Facility or Location")), # This is a component, so needs to be a super_link # - can't override field name, ondelete or requires super_link("site_id", "org_site", label = T("Warehouse/Facility/Office"), #filterby = "site_id", #filter_opts = auth.permitted_facilities(redirect_on_error=False), instance_types = auth.org_site_types, updateable = True, not_filterby = "obsolete", not_filter_opts = [True], #default = user.site_id if is_logged_in() else None, readable = True, writable = True, empty = False, represent = self.org_site_represent, #widget = S3SiteAutocompleteWidget(), comment = SCRIPT( '''$(document).ready(function(){ S3FilterFieldChange({ 'FilterField':'organisation_id', 'Field':'site_id', 'FieldPrefix':'org', 'FieldResource':'site', 'FieldID':'site_id', 'fncRepresent': function(record, PrepResult) { var InstanceTypeNice = %(instance_type_nice)s; return record.name + " (" + InstanceTypeNice[record.instance_type] + ")"; } }) })''' % dict(instance_type_nice = auth.org_site_types)), ), self.org_room_id(), #location_id(), Field("cancel", "boolean", default = False, label = T("Cancel Log Entry"), comment = DIV(_class="tooltip", _title="%s|%s" % (T("Cancel Log Entry"), T("'Cancel' will indicate an asset log entry did not occur"))) ), Field("cond", "integer", # condition is a MySQL reserved word requires = IS_IN_SET(asset_condition_opts, zero = "%s..." % T("Please select")), represent = lambda opt: \ asset_condition_opts.get(opt, UNKNOWN_OPT), label = T("Condition")), person_id("by_person_id", label = T("Assigned By"), # This can either be the Asset controller if signed-out from the store default = auth.s3_logged_in_person(), # or the previous owner if passed on directly (e.g. to successor in their post) comment = self.pr_person_comment(child="by_person_id"), ), s3_comments(), *s3_meta_fields()) # CRUD strings ADD_ASSIGN = T("New Entry in Asset Log") crud_strings[tablename] = Storage( title_create=ADD_ASSIGN, title_display=T("Asset Log Details"), title_list=T("Asset Log"), title_update=T("Edit Asset Log Entry"), title_search=T("Search Asset Log"), subtitle_create=ADD_ASSIGN, label_list_button=T("Asset Log"), label_create_button=ADD_ASSIGN, label_delete_button=T("Delete Asset Log Entry"), msg_record_created=T("Entry added to Asset Log"), msg_record_modified=T("Asset Log Entry updated"), msg_record_deleted=T("Asset Log Entry deleted"), msg_list_empty=T("Asset Log Empty")) # Resource configuration configure( tablename, onvalidation=self.asset_log_onvalidation, onaccept=self.asset_log_onaccept, listadd=False, list_fields=[ "id", "status", "datetime", "datetime_until", "organisation_id", "site_id", "room_id", #"location_id", "cancel", "cond", "comments" ]) # --------------------------------------------------------------------- # Pass variables back to global scope (s3db.*) # return Storage( asset_asset_id=asset_id, asset_rheader=asset_rheader, asset_represent=self.asset_represent, asset_log_prep=self.asset_log_prep, )
def model(self): T = current.T db = current.db add_component = self.add_component configure = self.configure crud_strings = current.response.s3.crud_strings define_table = self.define_table # --------------------------------------------------------------------- # Series # - lists of Posts displaying in recent-first mode # tablename = "cms_series" table = define_table( tablename, Field("name", notnull=True, label=T("Name")), Field("avatar", "boolean", default=False, represent=s3_yes_no_represent, label=T("Show author picture?")), Field("replies", "boolean", default=False, represent=s3_yes_no_represent, label=T("Comments permitted?")), s3_comments(), # Multiple Roles (@ToDo: Implement the restriction) s3_roles_permitted(readable=False, writable=False), *s3_meta_fields()) # CRUD Strings ADD_SERIES = T("Add Series") crud_strings[tablename] = Storage( title_create=ADD_SERIES, title_display=T("Series Details"), title_list=T("Series"), title_update=T("Edit Series"), title_search=T("Search Series"), title_upload=T("Import Series"), subtitle_create=T("Add New Series"), label_list_button=T("List Series"), label_create_button=ADD_SERIES, msg_record_created=T("Series added"), msg_record_modified=T("Series updated"), msg_record_deleted=T("Series deleted"), msg_list_empty=T("No series currently defined")) # Reusable field series_id = S3ReusableField("series_id", table, readable=False, writable=False, requires=IS_NULL_OR( IS_ONE_OF(db, "cms_series.id", "%(name)s")), ondelete="CASCADE") # Resource Configuration configure(tablename, onaccept=self.series_onaccept, create_next=URL(f="series", args=["[id]", "post"])) # Components add_component("cms_post", cms_series="series_id") # --------------------------------------------------------------------- # Posts # - single blocks of rich text which can be embedded into a page, # be viewed as full pages or as part of a Series # modules = {} _modules = current.deployment_settings.modules for module in _modules: if module in ["appadmin", "errors", "sync"]: continue modules[module] = _modules[module].name_nice tablename = "cms_post" table = define_table(tablename, series_id(), Field("module", requires=IS_NULL_OR( IS_IN_SET_LAZY(lambda: \ sort_dict_by_values(modules))), comment=T("If you specify a module then this will be used as the text in that module's index page"), label=T("Module")), Field("name", notnull=True, comment=T("This isn't visible to the published site, but is used to allow menu items to point to the page"), label=T("Name")), Field("title", comment=T("The title of the page, as seen in the browser (optional)"), label=T("Title")), Field("body", "text", notnull=True, widget = s3_richtext_widget, label=T("Body")), Field("avatar", "boolean", default=False, represent = s3_yes_no_represent, label=T("Show author picture?")), Field("replies", "boolean", default=False, represent = s3_yes_no_represent, label=T("Comments permitted?")), #Field("published", "boolean", # default=True, # label=T("Published")), s3_comments(), # Multiple Roles (@ToDo: Implement the restriction) s3_roles_permitted( readable = False, writable = False ), *s3_meta_fields()) # CRUD Strings ADD_POST = T("Add Post") crud_strings[tablename] = Storage( title_create=ADD_POST, title_display=T("Post Details"), title_list=T("Posts"), title_update=T("Edit Post"), title_search=T("Search Posts"), title_upload=T("Import Posts"), subtitle_create=T("Add New Post"), label_list_button=T("List Posts"), label_create_button=ADD_POST, msg_record_created=T("Post added"), msg_record_modified=T("Post updated"), msg_record_deleted=T("Post deleted"), msg_list_empty=T("No posts currently defined")) # Reusable field post_id = S3ReusableField("post_id", table, label = T("Post"), sortby="name", requires = IS_NULL_OR( IS_ONE_OF(db, "cms_post.id", "%(name)s")), represent = lambda id, row=None: \ (id and [db.cms_post[id].name] or [NONE])[0], comment = S3AddResourceLink(c="cms", f="post", title=ADD_POST, tooltip=T("A block of rich text which could be embedded into a page, viewed as a complete page or viewed as a list of news items.")), ondelete = "CASCADE") # Resource Configuration configure(tablename, onaccept=self.post_onaccept) # Components add_component("cms_comment", cms_post="post_id") # --------------------------------------------------------------------- # Tags # tablename = "cms_tag" table = define_table( tablename, Field("name", label=T("Tag")), s3_comments(), # Multiple Roles (@ToDo: Implement the restriction) #s3_roles_permitted( # readable = False, # writable = False # ), *s3_meta_fields()) # CRUD Strings ADD_TAG = T("Add Tag") crud_strings[tablename] = Storage( title_create=ADD_TAG, title_display=T("Tag Details"), title_list=T("Tags"), title_update=T("Edit Tag"), title_search=T("Search Tags"), title_upload=T("Import Tags"), subtitle_create=T("Add New Tag"), label_list_button=T("List Tags"), label_create_button=ADD_TAG, msg_record_created=T("Tag added"), msg_record_modified=T("Tag updated"), msg_record_deleted=T("Tag deleted"), msg_list_empty=T("No tags currently defined")) # --------------------------------------------------------------------- # Tags <> Posts link table # tablename = "cms_tag" table = define_table(tablename, post_id(), Field("tag_id", "reference cms_tag"), *s3_meta_fields()) # CRUD Strings ADD_TAG = T("Tag Post") crud_strings[tablename] = Storage( title_create=ADD_TAG, title_display=T("Tag Details"), title_list=T("Tags"), title_update=T("Edit Tag"), title_search=T("Search Tags"), title_upload=T("Import Tags"), subtitle_create=T("Add New Tag"), label_list_button=T("List Tagged Posts"), label_create_button=ADD_TAG, msg_record_created=T("Post Tagged"), msg_record_modified=T("Tag updated"), msg_record_deleted=T("Tag removed"), msg_list_empty=T("No posts currently tagged")) # --------------------------------------------------------------------- # Comments # - threaded comments on Posts # # @ToDo: Attachments? # # Parent field allows us to: # * easily filter for top-level threads # * easily filter for next level of threading # * hook a new reply into the correct location in the hierarchy # tablename = "cms_comment" table = define_table( tablename, Field("parent", "reference cms_comment", requires=IS_NULL_OR(IS_ONE_OF(db, "cms_comment.id")), readable=False), post_id(), Field("body", "text", notnull=True, label=T("Comment")), *s3_meta_fields()) # Resource Configuration configure(tablename, list_fields=["id", "post_id", "created_by", "modified_on"]) # --------------------------------------------------------------------- # Pass variables back to global scope (s3db.*) # return Storage()