def customise_event_event_resource(r, tablename): from gluon import IS_EMPTY_OR, IS_INT_IN_RANGE from s3 import S3LocationSelector, S3SQLCustomForm, S3SQLInlineComponent s3db = current.s3db s3db.event_event_location.location_id.widget = \ S3LocationSelector(levels=("L1", "L2")) # Cat 1: Extra-ordinary # Cat 2: Large # Cat 3: Medium # Cat 4: Small s3db.event_event_tag.value.requires = IS_EMPTY_OR(IS_INT_IN_RANGE( 1, 5)) crud_form = S3SQLCustomForm( "name", "event_type_id", "start_date", S3SQLInlineComponent( "tag", fields=[ ("", "value"), ], filterby={ "field": "tag", "options": "category", }, label=T("Category"), multiple=False, ), "closed", "comments", ) list_fields = [ "name", "event_type_id", "start_date", (T("Category"), "tag.value"), "closed", "comments", ] # If we have default ones defined then need to add them in a cascade: #onaccept = s3db.get_config("event_event", "onaccept") #ondelete = s3db.get_config("event_event", "ondelete") onaccept = lambda form: response_locations() update_onaccept = s3db.get_config("event_event", "update_onaccept") update_onaccept = [update_onaccept, onaccept] s3db.configure( "event_event", crud_form=crud_form, list_fields=list_fields, onaccept=onaccept, ondelete=onaccept, update_onaccept=update_onaccept, )
def define_tables(self, db, migrate): from gluon.dal import DEFAULT logger.debug('defining tables (migrate=%s)', migrate) now = self.now db.define_table('scheduler_task', Field('application_name', requires=IS_NOT_EMPTY(), default=None, writable=False), Field('task_name', default=None), Field('group_name', default='main'), Field('status', requires=IS_IN_SET(TASK_STATUS), default=QUEUED, writable=False), Field('function_name', requires=IS_IN_SET(sorted(self.tasks.keys())) if self.tasks else DEFAULT), Field('uuid', length=255, requires=IS_NOT_IN_DB(db, 'scheduler_task.uuid'), unique=True, default=web2py_uuid), Field('args', 'text', default='[]', requires=TYPE(list)), Field('vars', 'text', default='{}', requires=TYPE(dict)), Field('enabled', 'boolean', default=True), Field('start_time', 'datetime', default=now, requires=IS_DATETIME()), Field('next_run_time', 'datetime', default=now), Field('stop_time', 'datetime'), Field('repeats', 'integer', default=1, comment="0=unlimited", requires=IS_INT_IN_RANGE(0, None)), Field('retry_failed', 'integer', default=0, comment="-1=unlimited", requires=IS_INT_IN_RANGE(-1, None)), Field('period', 'integer', default=60, comment='seconds', requires=IS_INT_IN_RANGE(0, None)), Field('prevent_drift', 'boolean', default=False, comment='Cron-like start_times between runs'), Field('timeout', 'integer', default=60, comment='seconds', requires=IS_INT_IN_RANGE(0, None)), Field('sync_output', 'integer', default=0, comment="update output every n sec: 0=never", requires=IS_INT_IN_RANGE(0, None)), Field('times_run', 'integer', default=0, writable=False), Field('times_failed', 'integer', default=0, writable=False), Field('last_run_time', 'datetime', writable=False, readable=False), Field('assigned_worker_name', default='', writable=False), on_define=self.set_requirements, migrate=self.__get_migrate('scheduler_task', migrate), format='%(task_name)s') db.define_table('scheduler_run', Field('task_id', 'reference scheduler_task'), Field('status', requires=IS_IN_SET(RUN_STATUS)), Field('start_time', 'datetime'), Field('stop_time', 'datetime'), Field('run_output', 'text'), Field('run_result', 'text'), Field('traceback', 'text'), Field('worker_name', default=self.worker_name), migrate=self.__get_migrate('scheduler_run', migrate)) db.define_table('scheduler_worker', Field('worker_name', length=255, unique=True), Field('first_heartbeat', 'datetime'), Field('last_heartbeat', 'datetime'), Field('status', requires=IS_IN_SET(WORKER_STATUS)), Field('is_ticker', 'boolean', default=False, writable=False), Field('group_names', 'list:string', default=self.group_names), migrate=self.__get_migrate('scheduler_worker', migrate)) if migrate is not False: db.commit()
def configure_tasktable_crud( self, task=None, function=None, args=None, vars=None, period=3600, # seconds, so 1 hour status_writable=False, ): """ Configure the task table for interactive CRUD, setting defaults, widgets and hiding unnecessary fields @param task: the task name (will use a UUID if omitted) @param function: the function name (won't hide if omitted) @param args: the function position arguments @param vars: the function named arguments @param period: the default period for tasks @param status_writable: make status and next run time editable """ T = current.T NONE = current.messages["NONE"] UNLIMITED = T("unlimited") tablename = self.TASK_TABLENAME table = current.db[tablename] # Configure start/stop time fields for fn in ("start_time", "stop_time"): field = table[fn] field.represent = lambda dt: \ S3DateTime.datetime_represent(dt, utc=True) set_min = set_max = None if fn == "start_time": field.requires = IS_UTC_DATETIME() set_min = "#scheduler_task_stop_time" elif fn == "stop_time": field.requires = IS_EMPTY_OR(IS_UTC_DATETIME()) set_max = "#scheduler_task_start_time" field.widget = S3CalendarWidget( past=0, set_min=set_min, set_max=set_max, timepicker=True, ) # Task name (default use UUID) if task is None: from uuid import uuid4 task = str(uuid4()) field = table.task_name field.default = task field.readable = field.writable = False # Function (default+hide if specified as parameter) if function: field = table.function_name field.default = function field.readable = field.writable = False # Args and vars if isinstance(args, list): field = table.args field.default = json.dumps(args) field.readable = field.writable = False else: field.default = "[]" if isinstance(vars, dict): field = table.vars field.default = json.dumps(vars) field.readable = field.writable = False else: field.default = {} # Fields which are always editable field = table.repeats field.label = T("Repeat") field.comment = T("times (0 = unlimited)") field.default = 0 field.represent = lambda opt: \ opt and "%s %s" % (opt, T("times")) or \ opt == 0 and UNLIMITED or \ NONE field = table.period field.label = T("Run every") field.default = period field.widget = S3TimeIntervalWidget.widget field.requires = IS_INT_IN_RANGE(0, None) field.represent = S3TimeIntervalWidget.represent field.comment = None table.timeout.default = 600 table.timeout.represent = lambda opt: \ opt and "%s %s" % (opt, T("seconds")) or \ opt == 0 and UNLIMITED or \ NONE # Always use "default" controller (web2py uses current controller), # otherwise the anonymous worker does not pass the controller # permission check and gets redirected to login before it reaches # the task function which does the s3_impersonate field = table.application_name field.default = "%s/default" % current.request.application field.readable = field.writable = False # Hidden fields hidden = ( "uuid", "broadcast", "group_name", "times_run", "assigned_worker_name", "sync_output", "times_failed", "cronline", ) for fn in hidden: table[fn].readable = table[fn].writable = False # Optionally editable fields fields = ("next_run_time", "status", "prevent_drift") for fn in fields: table[fn].readable = table[fn].writable = status_writable list_fields = [ "id", "enabled", "start_time", "repeats", "period", (T("Last run"), "last_run_time"), (T("Last status"), "status"), (T("Next run"), "next_run_time"), "stop_time" ] if not function: list_fields[1:1] = ["task_name", "function_name"] current.s3db.configure( tablename, list_fields=list_fields, ) response = current.response if response: response.s3.crud_strings[tablename] = Storage( label_create=T("Create Job"), title_display=T("Job Details"), title_list=T("Job Schedule"), title_update=T("Edit Job"), label_list_button=T("List Jobs"), msg_record_created=T("Job added"), msg_record_modified=T("Job updated"), msg_record_deleted=T("Job deleted"), msg_list_empty=T("No jobs configured yet"), msg_no_match=T("No jobs configured"))
def formfields(): """ Generate the form fields for the registration form @returns: a tuple (formfields, required_fields, subheadings) - formfields = list of form fields - required_fields = list of field names of required fields - subheadings = list of tuples (position, heading) to insert into the form """ T = current.T request = current.request db = current.db s3db = current.s3db auth = current.auth auth_settings = auth.settings auth_messages = auth.messages utable = auth_settings.table_user passfield = auth_settings.password_field occupation_type_represent = S3Represent( lookup="pr_occupation_type", multiple=True, ) # Instantiate Consent Tracker consent = s3db.auth_Consent(processing_types=["SHARE"]) # Last name is required utable.last_name.requires = IS_NOT_EMPTY( error_message=T("input required")) ltable = s3db.gis_location # Form fields formfields = [utable.first_name, utable.last_name, s3_date("date_of_birth", label = T("Date of Birth"), future = -156, empty = False, ), # -------------------------------------------- utable.email, utable[passfield], # Password Verification Field Field("password_two", "password", label = auth_messages.verify_password, requires = IS_EXPR("value==%s" % \ repr(request.vars.get(passfield)), error_message = auth_messages.mismatched_password, ), comment = DIV(_class = "tooltip", _title = "%s|%s" % (auth_messages.verify_password, T("Enter the same password again"), ), ), ), # -------------------------------------------- Field("home_phone", label = T("Phone"), requires = IS_EMPTY_OR(IS_PHONE_NUMBER_MULTI()), ), Field("mobile_phone", label = T("Mobile Phone"), requires = IS_EMPTY_OR(IS_PHONE_NUMBER_SINGLE()), ), #Field("office_phone", # label = T("Office Phone"), # requires = IS_EMPTY_OR(IS_PHONE_NUMBER_MULTI()), # ), # -------------------------------------------- s3db.gis_location_id("location_id", widget = S3LocationSelector( show_address = False, show_postcode = False, show_map = False, ), ), Field("addr_street", label = ltable.addr_street.label, ), Field("addr_postcode", label = ltable.addr_postcode.label, requires = IS_NOT_EMPTY(), ), # -------------------------------------------- Field("occupation_type_ids", "list:reference pr_occupation_type", label = T("Occupation Type"), requires = IS_EMPTY_OR(IS_ONE_OF(db, "pr_occupation_type.id", occupation_type_represent, multiple=True, )), represent = occupation_type_represent, widget = S3MultiSelectWidget(), comment = DIV(_class = "tooltip", _title = "%s|%s" % (T("Occupation Type"), T("Select all that apply"), ), ), ), Field("occupation", label = T("Occupation / Speciality"), comment = DIV(_class = "tooltip", _title = "%s|%s" % (T("Occupation / Speciality"), T("Specify your exact job designation"), ), ), ), # -------------------------------------------- s3_date("start_date", label = T("Available from"), default = "now", past = 0, set_min = "#auth_user_start_date", ), s3_date("end_date", label = T("Available until"), past = 0, set_max = "#auth_user_start_date", ), Field("hours_per_week", "integer", label = T("Hours per Week"), requires = IS_EMPTY_OR(IS_INT_IN_RANGE(1, 60)), comment = DIV(_class = "tooltip", _title = "%s|%s" % (T("Hours per Week"), T("Specify the maximum number of weekly hours"), ), ), ), Field("schedule", "text", label = T("Availability Schedule"), widget = s3_comments_widget, comment = DIV(_class = "tooltip", _title = "%s|%s" % (T("Availability Schedule"), T("Specify days/hours like: Monday 10-12; Tuesday 10-12 and 14-19; Friday 13-15"), ), ), ), s3db.hrm_multi_skill_id( label = T("Skills / Resources"), widget = S3GroupedOptionsWidget(cols = 1, size = None, help_field = "comments", ), ), # -------------------------------------------- Field("comments", "text", label = T("Comments"), widget = s3_comments_widget, ), # -------------------------------------------- Field("consent", label = T("Consent"), widget = consent.widget, ), ] # Required fields required_fields = [ "first_name", "last_name", ] # Subheadings subheadings = ( (3, T("User Account")), (6, T("Contact Information")), (8, T("Address")), (11, T("Occupation")), (13, T("Availability and Resources")), (18, T("Comments")), (19, T("Privacy")), ) return formfields, required_fields, subheadings
def define_tables(self, db, migrate): from gluon import current from gluon.dal import DEFAULT logging.debug('defining tables (migrate=%s)' % migrate) now = self.now() db.define_table('scheduler_task', Field('application_name', requires=IS_NOT_EMPTY(), default=None, writable=False), Field('task_name', default=None), Field('group_name', default='main', writable=False), Field('status', requires=IS_IN_SET(TASK_STATUS), default=QUEUED, writable=False), Field('function_name', requires=IS_IN_SET(sorted(self.tasks.keys())) if self.tasks else DEFAULT), Field('uuid', requires=IS_NOT_IN_DB(db, 'scheduler_task.uuid'), unique=True, default=web2py_uuid), Field('args', 'text', default='[]', requires=TYPE(list)), Field('vars', 'text', default='{}', requires=TYPE(dict)), Field('enabled', 'boolean', default=True), Field('start_time', 'datetime', default=now, requires=IS_NOT_EMPTY()), Field('next_run_time', 'datetime', default=now), Field('stop_time', 'datetime'), Field('repeats', 'integer', default=1, comment="0=unlimited", requires=IS_INT_IN_RANGE(0, None)), Field('retry_failed', 'integer', default=0, comment="-1=unlimited", requires=IS_INT_IN_RANGE(-1, None)), Field('period', 'integer', default=60, comment='seconds', requires=IS_INT_IN_RANGE(0, None)), Field('timeout', 'integer', default=60, comment='seconds', requires=IS_INT_IN_RANGE(0, None)), Field('sync_output', 'integer', default=0, comment="update output every n sec: 0=never", requires=IS_INT_IN_RANGE(0, None)), Field('times_run', 'integer', default=0, writable=False), Field('times_failed', 'integer', default=0, writable=False), Field('last_run_time', 'datetime', writable=False, readable=False), Field('assigned_worker_name', default='', writable=False), migrate=migrate, format='%(task_name)s') if hasattr(current, 'request'): db.scheduler_task.application_name.default = '%s/%s' % ( current.request.application, current.request.controller) db.define_table('scheduler_run', Field('scheduler_task', 'reference scheduler_task'), Field('status', requires=IS_IN_SET(RUN_STATUS)), Field('start_time', 'datetime'), Field('stop_time', 'datetime'), Field('output', 'text'), Field('result', 'text'), Field('traceback', 'text'), Field('worker_name', default=self.worker_name), migrate=migrate) db.define_table('scheduler_worker', Field('worker_name', unique=True), Field('first_heartbeat', 'datetime'), Field('last_heartbeat', 'datetime'), Field('status', requires=IS_IN_SET(WORKER_STATUS)), Field('is_ticker', 'boolean', default=False, writable=False), Field('group_names', 'list:string', default=self.group_names), migrate=migrate) db.commit()
def prep(r): SURPLUS_MEALS = "SURPLUS-MEALS" T = current.T db = current.db s3db = current.s3db resource = r.resource # Set default SURPLUS_MEALS event type ttable = s3db.dvr_case_event_type query = (ttable.code == SURPLUS_MEALS) & \ (ttable.deleted != True) event_type = db(query).select( ttable.id, limitby=(0, 1), ).first() if not event_type: r.error(400, "No event type with code %s defined" % SURPLUS_MEALS) event_type_id = event_type.id # Filter to SURPLUS_MEALS events without person_id query = (FS("type_id") == event_type_id) & \ (FS("person_id") == None) resource.add_filter(query) # Configure fields table = resource.table field = table.person_id field.default = None field.readable = field.writable = False field = table.type_id field.default = event_type_id field.readable = field.writable = False field = table.date field.readable = field.writable = True field = table.quantity field.default = 0 # Override IS_EMPTY_OR field.requires = IS_INT_IN_RANGE(0, None) field.readable = field.writable = True field = table.modified_by field.readable = True registered_by = (T("Registered by"), "modified_by") if r.interactive: # Custom CRUD form crud_form = S3SQLCustomForm( "date", "quantity", registered_by, "comments", ) # Custom filter widgets filter_widgets = [ S3TextFilter( [ "created_by$email", "comments", ], label=T("Search"), ), S3DateFilter("date"), ] resource.configure( crud_form=crud_form, filter_widgets=filter_widgets, ) # Turn off filter manager current.deployment_settings.search.filter_manager = False # Custom list fields list_fields = [ "date", "quantity", registered_by, "comments", ] # URL of the list view list_url = URL(args=[controller], vars={}) s3.datatable_ajax_source = list_url resource.configure( insertable=True, list_fields=list_fields, # Fix redirects: create_next=list_url, update_next=list_url, delete_next=list_url, ) # Custom CRUD strings T = current.T s3.crud_strings["dvr_case_event"] = Storage( label_create=T("Register Surplus Meals Quantity"), title_display=T("Surplus Meals Quantity"), title_list=T("Surplus Meals"), title_update=T("Edit Surplus Meals Quantity"), label_list_button=T("List Surplus Meals"), label_delete_button=T("Delete Entry"), msg_record_created=T("Entry added"), msg_record_modified=T("Entry updated"), msg_record_deleted=T("Entry deleted"), msg_list_empty=T("No Surplus Meals currently registered"), ) return True
def model(self): T = current.T auth = current.auth super_link = self.super_link #root_org = auth.root_org() #ADMIN = current.session.s3.system_roles.ADMIN #is_admin = auth.s3_has_role(ADMIN) # --------------------------------------------------------------------- # Area # tablename = "po_area" self.define_table( tablename, super_link("doc_id", "doc_entity"), # This was included to allow Areas to be realm entities but this is currently not used # Re-enable onaccept/ondelete & S3EntityRoleManager if this becomes required in future #super_link("pe_id", "pr_pentity"), Field( "name", requires=IS_NOT_EMPTY(), ), self.gis_location_id(widget=S3LocationSelector( points=False, polygons=True, feature_required=True, ), ), # Included primarily to set realm self.org_organisation_id( default=auth.user and auth.user.organisation_id, #default = root_org, #readable = is_admin, #writable = is_admin, ), Field( "attempted_visits", "integer", comment=DIV( _class="tooltip", _title="%s|%s" % (T("Attempted Visits"), T("Number of households in the area where nobody was at home at the time of visit" ))), default=0, label=T("Attempted Visits"), requires=IS_EMPTY_OR(IS_INT_IN_RANGE(minimum=0)), ), s3_comments(), *s3_meta_fields()) # CRUD Strings current.response.s3.crud_strings[tablename] = Storage( label_create=T("Create Area"), title_display=T("Area Details"), title_list=T("Areas"), title_update=T("Edit Area"), label_list_button=T("List Areas"), label_delete_button=T("Delete Area"), msg_record_created=T("Area created"), msg_record_modified=T("Area updated"), msg_record_deleted=T("Area deleted"), msg_list_empty=T("No Areas currently registered"), ) # Reusable field represent = S3Represent(lookup=tablename, show_link=True) area_id = S3ReusableField( "area_id", "reference %s" % tablename, label=T("Area"), represent=represent, requires=IS_ONE_OF( current.db, "po_area.id", represent, ), sortby="name", comment=S3PopupLink( f="area", tooltip=T("Create a new area"), ), ) # Components self.add_components( tablename, po_household="area_id", org_organisation={ "link": "po_organisation_area", "joinby": "area_id", "key": "organisation_id", "actuate": "hide", }, ) levels = current.gis.get_relevant_hierarchy_levels() # Filters filter_widgets = [ S3TextFilter(["name"]), S3LocationFilter("location_id", levels=levels), ] # @todo: reports # Table Configuration self.configure( tablename, deduplicate=S3Duplicate(ignore_deleted=True), filter_widgets=filter_widgets, #onaccept = self.area_onaccept, #ondelete = self.area_ondelete, realm_components=("household", ), summary=( { "common": True, "name": "add", "widgets": [{ "method": "create" }], }, { "name": "table", "label": "Table", "widgets": [{ "method": "datatable" }] }, { "name": "map", "label": "Map", "widgets": [{ "method": "map", "ajax_init": True }], }, ), #super_entity = ("doc_entity", "pr_pentity"), super_entity="doc_entity", update_realm=True, ) # --------------------------------------------------------------------- # Pass names back to global scope (s3.*) # return { "po_area_id": area_id, }
def get_task_num_form(): form = FORM('Show:', INPUT(_name = 'task_num', _class='task_num', requires = IS_INT_IN_RANGE(1,101)), A(SPAN(_class='icon-refresh'), _onclick = 'tab_refresh();$(this).closest(\'form\').submit()', _href='#')) return form
def customise_project_activity_resource(r, tablename): s3db = current.s3db tablename = "project_activity" # Custom Filtered Components s3db.add_components( tablename, project_activity_organisation=( # Agency { "name": "agency", "joinby": "activity_id", "filterby": { "role": 1, }, #"multiple": False, }, # Partners { "name": "partner", "joinby": "activity_id", "filterby": { "role": 2, }, #"multiple": False, }, # Donors { "name": "donor", "joinby": "activity_id", "filterby": { "role": 3, }, #"multiple": False, }, ), project_activity_tag=( # Modality { "name": "modality", "joinby": "activity_id", "filterby": { "tag": "modality", }, "multiple": False, }, # Number { "name": "number", "joinby": "activity_id", "filterby": { "tag": "number", }, "multiple": False, }, )) # Individual settings for specific tag components from gluon import IS_EMPTY_OR, IS_IN_SET, IS_INT_IN_RANGE components_get = r.resource.components.get donor = components_get("donor") donor.table.organisation_id.default = None partner = components_get("partner") partner.table.organisation_id.default = None modality = components_get("modality") modality.table.value.requires = IS_EMPTY_OR( IS_IN_SET(("Cash", "In-kind"))) number = components_get("number") number.table.value.requires = IS_EMPTY_OR(IS_INT_IN_RANGE()) s3db.project_activity_data.unit.requires = IS_EMPTY_OR( IS_IN_SET(("People", "Households"))) from s3 import S3LocationFilter, S3OptionsFilter, S3SQLCustomForm, S3SQLInlineComponent, S3SQLInlineLink crud_form = S3SQLCustomForm( S3SQLInlineLink( "event", field="event_id", label=T("Disaster"), multiple=False, #required = True, ), S3SQLInlineComponent( "agency", name="agency", label=T("Agency"), fields=[ ("", "organisation_id"), ], #multiple = False, required=True, ), # @ToDo: MultiSelectWidget is nicer UI but S3SQLInlineLink # requires the link*ed* table as component (not the # link table as applied here) and linked components # cannot currently be filtered by link table fields # (=> should solve the latter rather than the former) # @ToDo: Fix Create Popups S3SQLInlineComponent( "partner", name="partner", label=T("Implementing Partner"), fields=[ ("", "organisation_id"), ], ), S3SQLInlineComponent( "donor", name="donor", label=T("Donor"), fields=[ ("", "organisation_id"), ], ), "location_id", S3SQLInlineLink( "sector", field="sector_id", filter=False, label=T("Sector"), multiple=False, ), (T("Relief Items/Activity"), "name"), S3SQLInlineComponent( "modality", name="modality", label=T("Modality"), fields=[ ("", "value"), ], multiple=False, ), S3SQLInlineComponent( "number", name="number", label=T("Number of Items/Kits/Activities"), fields=[ ("", "value"), ], multiple=False, ), (T("Activity Date (Planned/Start Date)"), "date"), (T("Activity Date (Completion Date)"), "end_date"), S3SQLInlineComponent( "activity_data", label="", fields=[ (T("People / Households"), "unit"), (T("Total Number People/HH Targeted"), "target_value"), (T("Total Number Of People/HH Reache"), "value"), ], multiple=False, ), (T("Activity Status"), "status_id"), "comments", ) filter_widgets = [ S3OptionsFilter("event.event_type_id"), S3OptionsFilter( "event__link.event_id" ), # @ToDo: Filter this list dynamically based on Event Type S3OptionsFilter("sector_activity.sector_id"), S3LocationFilter( "location_id", # These levels are for SHARE/LK levels=("L2", "L3", "L4"), ), S3OptionsFilter( "status_id", cols=4, label=T("Status"), ), ] s3db.configure( tablename, crud_form=crud_form, filter_widgets=filter_widgets, list_fields=[ (T("Disaster"), "event__link.event_id"), (T("Agency"), "agency.organisation_id"), (T("Implementing Partner"), "partner.organisation_id"), (T("Donor"), "donor.organisation_id"), (T("District"), "location_id$L1"), (T("DS Division"), "location_id$L2"), (T("GN Division"), "location_id$L3"), (T("Sector"), "sector_activity.sector_id"), (T("Relief Items/Activity"), "name"), (T("Modality"), "modality.value"), (T("Number of Items/Kits/Activities"), "number.value"), (T("Activity Date (Planned/Start Date)"), "date"), (T("Activity Date (Completion Date)"), "end_date"), (T("People / Households"), "activity_data.unit"), (T("Total Number People/HH Targeted"), "activity_data.target_value"), (T("Total Number Of People/HH Reached"), "activity_data.value"), (T("Activity Status"), "status_id"), "comments", ], )