2552 lines
116 KiB
Python
2552 lines
116 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from collections import OrderedDict
|
|
|
|
from gluon import current, URL
|
|
from gluon.storage import Storage
|
|
|
|
from s3 import S3ReportRepresent
|
|
|
|
def config(settings):
|
|
"""
|
|
Settings for the SHARE Template
|
|
|
|
Migration Issues:
|
|
req_need.name is now length=64
|
|
(SHARE can use req_need.description instead if the notnull=True removed)
|
|
"""
|
|
|
|
T = current.T
|
|
|
|
settings.base.system_name = T("Humanitarian Country Team (HCT) Relief and Rehabilitation System")
|
|
settings.base.system_name_short = T("SHARE")
|
|
|
|
# UI Settings
|
|
settings.ui.menu_logo = URL(c = "static",
|
|
f = "themes",
|
|
args = ["SHARE", "img", "sharemenulogo.png"],
|
|
)
|
|
|
|
# PrePopulate data
|
|
settings.base.prepopulate.append("SHARE")
|
|
settings.base.prepopulate_demo.append("SHARE/Demo")
|
|
|
|
# Theme (folder to use for views/layout.html)
|
|
settings.base.theme = "SHARE"
|
|
|
|
# Authentication settings
|
|
# Should users be allowed to register themselves?
|
|
#settings.security.self_registration = False
|
|
# Do new users need to verify their email address?
|
|
#settings.auth.registration_requires_verification = True
|
|
# Do new users need to be approved by an administrator prior to being able to login?
|
|
#settings.auth.registration_requires_approval = True
|
|
settings.auth.registration_requests_organisation = True
|
|
#settings.auth.registration_organisation_required = True
|
|
#settings.auth.registration_requests_site = True
|
|
|
|
settings.auth.registration_link_user_to = {"staff": T("Staff"),
|
|
"volunteer": T("Volunteer"),
|
|
#"member": T("Member")
|
|
}
|
|
|
|
def registration_organisation_default(default):
|
|
auth = current.auth
|
|
has_role = auth.s3_has_role
|
|
if has_role("ORG_ADMIN") and not has_role("ADMIN"):
|
|
return auth.user.organisation_id
|
|
else:
|
|
return default
|
|
|
|
settings.auth.registration_organisation_default = registration_organisation_default
|
|
|
|
# Approval emails get sent to all admins
|
|
settings.mail.approver = "ADMIN"
|
|
|
|
# Restrict the Location Selector to just certain countries
|
|
# NB This can also be over-ridden for specific contexts later
|
|
# e.g. Activities filtered to those of parent Project
|
|
#settings.gis.countries = ("US",)
|
|
# Uncomment to display the Map Legend as a floating DIV
|
|
settings.gis.legend = "float"
|
|
# Uncomment to Disable the Postcode selector in the LocationSelector
|
|
#settings.gis.postcode_selector = False # @ToDo: Vary by country (include in the gis_config!)
|
|
# Uncomment to show the Print control:
|
|
# http://eden.sahanafoundation.org/wiki/UserGuidelines/Admin/MapPrinting
|
|
#settings.gis.print_button = True
|
|
|
|
# GeoNames username
|
|
settings.gis.geonames_username = "trendspotter"
|
|
settings.gis.simplify_tolerance = 0
|
|
|
|
# L10n settings
|
|
# Number formats (defaults to ISO 31-0)
|
|
# Decimal separator for numbers (defaults to ,)
|
|
settings.L10n.decimal_separator = "."
|
|
# Thousands separator for numbers (defaults to space)
|
|
settings.L10n.thousands_separator = ","
|
|
|
|
# Security Policy
|
|
# http://eden.sahanafoundation.org/wiki/S3AAA#System-widePolicy
|
|
# 1: Simple (default): Global as Reader, Authenticated as Editor
|
|
# 2: Editor role required for Update/Delete, unless record owned by session
|
|
# 3: Apply Controller ACLs
|
|
# 4: Apply both Controller & Function ACLs
|
|
# 5: Apply Controller, Function & Table ACLs
|
|
# 6: Apply Controller, Function, Table ACLs and Entity Realm
|
|
# 7: Apply Controller, Function, Table ACLs and Entity Realm + Hierarchy
|
|
# 8: Apply Controller, Function, Table ACLs, Entity Realm + Hierarchy and Delegations
|
|
|
|
settings.security.policy = 6 # Controller, Function, Table ACLs and Entity Realm
|
|
|
|
# Don't show version info on About page
|
|
settings.security.version_info = False
|
|
|
|
# UI Settings
|
|
settings.ui.datatables_responsive = False
|
|
settings.ui.datatables_double_scroll = True
|
|
|
|
# Disable permalink
|
|
settings.ui.label_permalink = None
|
|
|
|
# Default summary pages:
|
|
settings.ui.summary = ({"common": True,
|
|
"name": "add",
|
|
"widgets": [{"method": "create"}],
|
|
},
|
|
{"name": "table",
|
|
"label": "Table",
|
|
"widgets": [{"method": "datatable"}],
|
|
},
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# CMS Content Management
|
|
#
|
|
settings.cms.bookmarks = True
|
|
settings.cms.richtext = True
|
|
settings.cms.show_tags = True
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Events
|
|
settings.event.label = "Disaster"
|
|
# Uncomment to not use Incidents under Events
|
|
settings.event.incident = False
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Messaging
|
|
settings.msg.parser = "SAMBRO" # for parse_tweet
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Organisations
|
|
settings.org.sector = True
|
|
# Show Organisation Types in the rheader
|
|
settings.org.organisation_type_rheader = True
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Projects
|
|
# Don't use Beneficiaries
|
|
settings.project.activity_beneficiaries = False
|
|
# Don't use Item Catalog for Distributions
|
|
settings.project.activity_items = False
|
|
settings.project.activity_sectors = True
|
|
# Links to Filtered Components for Donors & Partners
|
|
settings.project.organisation_roles = {
|
|
1: T("Organization"),
|
|
2: T("Implementing Partner"),
|
|
3: T("Donor"),
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Supply
|
|
# Disable the use of Multiple Item Catalogs
|
|
settings.supply.catalog_multi = False
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Comment/uncomment modules here to disable/enable them
|
|
# Modules menu is defined in modules/eden/menu.py
|
|
settings.modules = OrderedDict([
|
|
# Core modules which shouldn't be disabled
|
|
("default", Storage(
|
|
name_nice = "Home",
|
|
restricted = False, # Use ACLs to control access to this module
|
|
access = None, # All Users (inc Anonymous) can see this module in the default menu & access the controller
|
|
module_type = None # This item is not shown in the menu
|
|
)),
|
|
("admin", Storage(
|
|
name_nice = "Administration",
|
|
#description = "Site Administration",
|
|
restricted = True,
|
|
access = "|1|", # Only Administrators can see this module in the default menu & access the controller
|
|
module_type = None # This item is handled separately for the menu
|
|
)),
|
|
("appadmin", Storage(
|
|
name_nice = "Administration",
|
|
#description = "Site Administration",
|
|
restricted = True,
|
|
module_type = None # No Menu
|
|
)),
|
|
("errors", Storage(
|
|
name_nice = "Ticket Viewer",
|
|
#description = "Needed for Breadcrumbs",
|
|
restricted = False,
|
|
module_type = None # No Menu
|
|
)),
|
|
("setup", Storage(
|
|
name_nice = T("Setup"),
|
|
#description = "WebSetup",
|
|
restricted = True,
|
|
access = "|1|", # Only Administrators can see this module in the default menu & access the controller
|
|
module_type = None # No Menu
|
|
)),
|
|
("sync", Storage(
|
|
name_nice = "Synchronization",
|
|
#description = "Synchronization",
|
|
restricted = True,
|
|
access = "|1|", # Only Administrators can see this module in the default menu & access the controller
|
|
module_type = None # This item is handled separately for the menu
|
|
)),
|
|
#("tour", Storage(
|
|
# name_nice = T("Guided Tour Functionality"),
|
|
# module_type = None,
|
|
#)),
|
|
("translate", Storage(
|
|
name_nice = T("Translation Functionality"),
|
|
#description = "Selective translation of strings based on module.",
|
|
module_type = None,
|
|
)),
|
|
("gis", Storage(
|
|
name_nice = "Map",
|
|
#description = "Situation Awareness & Geospatial Analysis",
|
|
restricted = True,
|
|
module_type = 6, # 6th item in the menu
|
|
)),
|
|
("pr", Storage(
|
|
name_nice = "Person Registry",
|
|
#description = "Central point to record details on People",
|
|
restricted = True,
|
|
access = "|1|", # Only Administrators can see this module in the default menu (access to controller is possible to all still)
|
|
module_type = 10
|
|
)),
|
|
("org", Storage(
|
|
name_nice = "Organizations",
|
|
#description = 'Lists "who is doing what & where". Allows relief agencies to coordinate their activities',
|
|
restricted = True,
|
|
module_type = 1
|
|
)),
|
|
("hrm", Storage(
|
|
name_nice = "Staff",
|
|
#description = "Human Resources Management",
|
|
restricted = True,
|
|
module_type = 2,
|
|
)),
|
|
("vol", Storage(
|
|
name_nice = T("Volunteers"),
|
|
#description = "Human Resources Management",
|
|
restricted = True,
|
|
module_type = 2,
|
|
)),
|
|
("cms", Storage(
|
|
name_nice = "Content Management",
|
|
#description = "Content Management System",
|
|
restricted = True,
|
|
module_type = 10,
|
|
)),
|
|
("doc", Storage(
|
|
name_nice = "Documents",
|
|
#description = "A library of digital resources, such as photos, documents and reports",
|
|
restricted = True,
|
|
module_type = 10,
|
|
)),
|
|
("msg", Storage(
|
|
name_nice = "Messaging",
|
|
#description = "Sends & Receives Alerts via Email & SMS",
|
|
restricted = True,
|
|
# The user-visible functionality of this module isn't normally required. Rather it's main purpose is to be accessed from other modules.
|
|
module_type = None,
|
|
)),
|
|
("supply", Storage(
|
|
name_nice = "Supply Chain Management",
|
|
#description = "Used within Inventory Management, Request Management and Asset Management",
|
|
restricted = True,
|
|
module_type = None, # Not displayed
|
|
)),
|
|
("inv", Storage(
|
|
name_nice = T("Warehouses"),
|
|
#description = "Receiving and Sending Items",
|
|
restricted = True,
|
|
module_type = 4
|
|
)),
|
|
("asset", Storage(
|
|
name_nice = "Assets",
|
|
#description = "Recording and Assigning Assets",
|
|
restricted = True,
|
|
module_type = 5,
|
|
)),
|
|
# Vehicle depends on Assets
|
|
#("vehicle", Storage(
|
|
# name_nice = "Vehicles",
|
|
# #description = "Manage Vehicles",
|
|
# restricted = True,
|
|
# module_type = 10,
|
|
#)),
|
|
("req", Storage(
|
|
name_nice = "Requests",
|
|
#description = "Manage requests for supplies, assets, staff or other resources. Matches against Inventories where supplies are requested.",
|
|
restricted = True,
|
|
module_type = 10,
|
|
)),
|
|
# Used just for Statuses
|
|
("project", Storage(
|
|
name_nice = "Tasks",
|
|
#description = "Tracking of Projects, Activities and Tasks",
|
|
restricted = True,
|
|
module_type = 2
|
|
)),
|
|
#("cr", Storage(
|
|
# name_nice = T("Shelters"),
|
|
# #description = "Tracks the location, capacity and breakdown of victims in Shelters",
|
|
# restricted = True,
|
|
# module_type = 10
|
|
#)),
|
|
#("hms", Storage(
|
|
# name_nice = T("Hospitals"),
|
|
# #description = "Helps to monitor status of hospitals",
|
|
# restricted = True,
|
|
# module_type = 10
|
|
#)),
|
|
#("dvr", Storage(
|
|
# name_nice = T("Disaster Victim Registry"),
|
|
# #description = "Allow affected individuals & households to register to receive compensation and distributions",
|
|
# restricted = True,
|
|
# module_type = 10,
|
|
#)),
|
|
("event", Storage(
|
|
name_nice = "Events",
|
|
#description = "Activate Events (e.g. from Scenario templates) for allocation of appropriate Resources (Human, Assets & Facilities).",
|
|
restricted = True,
|
|
module_type = 10,
|
|
)),
|
|
#("transport", Storage(
|
|
# name_nice = T("Transport"),
|
|
# restricted = True,
|
|
# module_type = 10,
|
|
#)),
|
|
("stats", Storage(
|
|
name_nice = T("Statistics"),
|
|
#description = "Manages statistics",
|
|
restricted = True,
|
|
module_type = None,
|
|
)),
|
|
])
|
|
|
|
# -------------------------------------------------------------------------
|
|
def customise_cms_post_resource(r, tablename):
|
|
|
|
import json
|
|
|
|
from s3 import S3SQLCustomForm, S3SQLInlineComponent, \
|
|
S3DateFilter, S3OptionsFilter, S3TextFilter, \
|
|
s3_fieldmethod
|
|
|
|
s3db = current.s3db
|
|
|
|
# Virtual Field for Comments
|
|
# - otherwise need to do per-record DB calls inside cms_post_list_layout
|
|
# as direct list_fields come in unsorted, so can't match up to records
|
|
ctable = s3db.cms_comment
|
|
|
|
def comment_as_json(row):
|
|
body = row["cms_comment.body"]
|
|
if not body:
|
|
return None
|
|
return json.dumps({"body": body,
|
|
"created_by": row["cms_comment.created_by"],
|
|
"created_on": row["cms_comment.created_on"].isoformat(),
|
|
})
|
|
|
|
ctable.json_dump = s3_fieldmethod("json_dump",
|
|
comment_as_json,
|
|
# over-ride the default represent of s3_unicode to prevent HTML being rendered too early
|
|
#represent = lambda v: v,
|
|
)
|
|
|
|
s3db.configure("cms_comment",
|
|
extra_fields = ["body",
|
|
"created_by",
|
|
"created_on",
|
|
],
|
|
# Doesn't seem to have any impact
|
|
#orderby = "cms_comment.created_on asc",
|
|
)
|
|
|
|
|
|
table = s3db.cms_post
|
|
table.priority.readable = table.priority.writable = True
|
|
#table.series_id.readable = table.series_id.writable = True
|
|
#table.status_id.readable = table.status_id.writable = True
|
|
|
|
crud_form = S3SQLCustomForm(#(T("Type"), "series_id"),
|
|
(T("Priority"), "priority"),
|
|
#(T("Status"), "status_id"),
|
|
(T("Title"), "title"),
|
|
(T("Text"), "body"),
|
|
#(T("Location"), "location_id"),
|
|
# Tags are added client-side
|
|
S3SQLInlineComponent("document",
|
|
name = "file",
|
|
label = T("Files"),
|
|
fields = [("", "file"),
|
|
#"comments",
|
|
],
|
|
),
|
|
)
|
|
|
|
date_filter = S3DateFilter("date",
|
|
# If we introduce an end_date on Posts:
|
|
#["date", "end_date"],
|
|
label = "",
|
|
#hide_time = True,
|
|
#slider = True,
|
|
clear_text = "X",
|
|
)
|
|
date_filter.input_labels = {"ge": "Start Time/Date", "le": "End Time/Date"}
|
|
|
|
filter_widgets = [S3TextFilter(["body",
|
|
],
|
|
#formstyle = text_filter_formstyle,
|
|
label = T("Search"),
|
|
_placeholder = T("Enter search term…"),
|
|
),
|
|
#S3OptionsFilter("series_id",
|
|
# label = "",
|
|
# noneSelectedText = "Type", # T() added in widget
|
|
# no_opts = "",
|
|
# ),
|
|
S3OptionsFilter("priority",
|
|
label = "",
|
|
noneSelectedText = "Priority", # T() added in widget
|
|
no_opts = "",
|
|
),
|
|
#S3OptionsFilter("status_id",
|
|
# label = "",
|
|
# noneSelectedText = "Status", # T() added in widget
|
|
# no_opts = "",
|
|
# ),
|
|
S3OptionsFilter("created_by$organisation_id",
|
|
label = "",
|
|
noneSelectedText = "Source", # T() added in widget
|
|
no_opts = "",
|
|
),
|
|
S3OptionsFilter("tag_post.tag_id",
|
|
label = "",
|
|
noneSelectedText = "Tag", # T() added in widget
|
|
no_opts = "",
|
|
),
|
|
date_filter,
|
|
]
|
|
|
|
from templates.SHARE.controllers import cms_post_list_layout
|
|
|
|
s3db.configure("cms_post",
|
|
create_next = URL(args = [1, "post", "datalist"]),
|
|
crud_form = crud_form,
|
|
filter_widgets = filter_widgets,
|
|
list_fields = [#"series_id",
|
|
"priority",
|
|
#"status_id",
|
|
"date",
|
|
"title",
|
|
"body",
|
|
"created_by",
|
|
"tag.name",
|
|
"document.file",
|
|
"comment.json_dump",
|
|
],
|
|
list_layout = cms_post_list_layout,
|
|
)
|
|
|
|
settings.customise_cms_post_resource = customise_cms_post_resource
|
|
|
|
# -------------------------------------------------------------------------
|
|
def customise_event_sitrep_resource(r, tablename):
|
|
|
|
from s3 import s3_comments_widget
|
|
|
|
table = current.s3db.event_sitrep
|
|
|
|
table.name.widget = lambda f, v: \
|
|
s3_comments_widget(f, v, _placeholder = "Please provide a brief summary of the Situational Update you are submitting.")
|
|
|
|
table.comments.comment = None
|
|
table.comments.widget = lambda f, v: \
|
|
s3_comments_widget(f, v, _placeholder = "e.g. Any additional relevant information.")
|
|
|
|
current.response.s3.crud_strings[tablename] = Storage(
|
|
label_create = T("Add Situational Update"),
|
|
title_display = T("HCT Activity and Response Report"),
|
|
title_list = T("Situational Updates"),
|
|
title_update = T("Edit Situational Update"),
|
|
title_upload = T("Import Situational Updates"),
|
|
label_list_button = T("List Situational Updates"),
|
|
label_delete_button = T("Delete Situational Update"),
|
|
msg_record_created = T("Situational Update added"),
|
|
msg_record_modified = T("Situational Update updated"),
|
|
msg_record_deleted = T("Situational Update deleted"),
|
|
msg_list_empty = T("No Situational Updates currently registered"))
|
|
|
|
settings.customise_event_sitrep_resource = customise_event_sitrep_resource
|
|
# -----------------------------------------------------------------------------
|
|
def customise_event_sitrep_controller(**attr):
|
|
|
|
s3 = current.response.s3
|
|
|
|
# Custom postp
|
|
standard_postp = s3.postp
|
|
def postp(r, output):
|
|
# Call standard postp
|
|
if callable(standard_postp):
|
|
output = standard_postp(r, output)
|
|
|
|
if r.interactive:
|
|
# Mark this page to have differential CSS
|
|
s3.jquery_ready.append('''$('main').attr('id', 'sitrep')''')
|
|
|
|
return output
|
|
s3.postp = postp
|
|
|
|
# Extend the width of the Summary column
|
|
dt_col_widths = {0: 110,
|
|
1: 95,
|
|
2: 100,
|
|
3: 100,
|
|
4: 100,
|
|
5: 100,
|
|
6: 110,
|
|
7: 80,
|
|
8: 90,
|
|
9: 300,
|
|
10: 110,
|
|
}
|
|
if "dtargs" in attr:
|
|
attr["dtargs"]["dt_col_widths"] = dt_col_widths
|
|
else:
|
|
attr["dtargs"] = {"dt_col_widths": dt_col_widths,
|
|
}
|
|
|
|
return attr
|
|
|
|
settings.customise_event_sitrep_controller = customise_event_sitrep_controller
|
|
# -----------------------------------------------------------------------------
|
|
def customise_gis_location_controller(**attr):
|
|
|
|
s3 = current.response.s3
|
|
|
|
# Custom prep
|
|
standard_prep = s3.prep
|
|
def custom_prep(r):
|
|
|
|
# Call standard prep
|
|
if callable(standard_prep):
|
|
result = standard_prep(r)
|
|
else:
|
|
result = True
|
|
|
|
if r.representation == "json":
|
|
|
|
# Special filter vars to find child locations while
|
|
# including the parent location in the JSON result:
|
|
# adm => the parent location ID
|
|
# l => the target Lx level for child locations
|
|
get_vars = r.get_vars
|
|
adm = get_vars.get("adm")
|
|
if adm:
|
|
from s3 import FS
|
|
resource = r.resource
|
|
|
|
# Filter for children of adm
|
|
query = FS("parent") == adm
|
|
|
|
# Restrict children to a certain Lx level
|
|
level = get_vars.get("l")
|
|
if level:
|
|
q = FS("level") == level
|
|
query = (query & q) if query else q
|
|
|
|
# Always include adm
|
|
query = (FS("id") == adm) | query
|
|
resource.add_filter(query)
|
|
|
|
# Push the parent to top of the list + alpha-sort
|
|
table = resource.table
|
|
resource.configure(orderby = (table.level, table.name))
|
|
|
|
return result
|
|
s3.prep = custom_prep
|
|
|
|
return attr
|
|
|
|
settings.customise_gis_location_controller = customise_gis_location_controller
|
|
# -------------------------------------------------------------------------
|
|
def customise_msg_twitter_channel_resource(r, tablename):
|
|
|
|
s3db = current.s3db
|
|
def onaccept(form):
|
|
# Normal onaccept
|
|
s3db.msg_channel_onaccept(form)
|
|
_id = form.vars.id
|
|
db = current.db
|
|
table = db.msg_twitter_channel
|
|
channel_id = db(table.id == _id).select(table.channel_id,
|
|
limitby=(0, 1)).first().channel_id
|
|
# Link to Parser
|
|
table = s3db.msg_parser
|
|
_id = table.insert(channel_id=channel_id, function_name="parse_tweet", enabled=True)
|
|
s3db.msg_parser_enable(_id)
|
|
|
|
run_async = current.s3task.run_async
|
|
# Poll
|
|
run_async("msg_poll", args=["msg_twitter_channel", channel_id])
|
|
|
|
# Parse
|
|
run_async("msg_parse", args=[channel_id, "parse_tweet"])
|
|
|
|
s3db.configure(tablename,
|
|
create_onaccept = onaccept,
|
|
)
|
|
|
|
settings.customise_msg_twitter_channel_resource = customise_msg_twitter_channel_resource
|
|
|
|
# -------------------------------------------------------------------------
|
|
def customise_org_organisation_resource(r, tablename):
|
|
|
|
s3db = current.s3db
|
|
|
|
# Custom Components
|
|
s3db.add_components(tablename,
|
|
org_organisation_tag = (# Request Number
|
|
{"name": "req_number",
|
|
"joinby": "organisation_id",
|
|
"filterby": {"tag": "req_number",
|
|
},
|
|
"multiple": False,
|
|
},
|
|
# Vision
|
|
{"name": "vision",
|
|
"joinby": "organisation_id",
|
|
"filterby": {"tag": "vision",
|
|
},
|
|
"multiple": False,
|
|
},
|
|
),
|
|
)
|
|
|
|
from s3 import S3SQLCustomForm, S3SQLInlineComponent, S3SQLInlineLink, s3_comments_widget
|
|
|
|
# Individual settings for specific tag components
|
|
components_get = s3db.resource(tablename).components.get
|
|
|
|
vision = components_get("vision")
|
|
vision.table.value.widget = s3_comments_widget
|
|
|
|
crud_form = S3SQLCustomForm("name",
|
|
"acronym",
|
|
S3SQLInlineLink("organisation_type",
|
|
field = "organisation_type_id",
|
|
# Default 10 options just triggers which adds unnecessary complexity to a commonly-used form & commonly an early one (create Org when registering)
|
|
search = False,
|
|
label = T("Type"),
|
|
multiple = False,
|
|
widget = "multiselect",
|
|
),
|
|
S3SQLInlineLink("sector",
|
|
columns = 4,
|
|
field = "sector_id",
|
|
label = T("Sectors"),
|
|
),
|
|
#S3SQLInlineLink("service",
|
|
# columns = 4,
|
|
# field = "service_id",
|
|
# label = T("Services"),
|
|
# ),
|
|
"country",
|
|
"phone",
|
|
"website",
|
|
"logo",
|
|
(T("About"), "comments"),
|
|
S3SQLInlineComponent("vision",
|
|
label = T("Vision"),
|
|
fields = [("", "value")],
|
|
multiple = False,
|
|
),
|
|
S3SQLInlineComponent("req_number",
|
|
label = T("Request Number"),
|
|
fields = [("", "value")],
|
|
multiple = False,
|
|
),
|
|
)
|
|
|
|
s3db.configure(tablename,
|
|
crud_form = crud_form,
|
|
)
|
|
|
|
settings.customise_org_organisation_resource = customise_org_organisation_resource
|
|
|
|
# -------------------------------------------------------------------------
|
|
def customise_org_sector_controller(**attr):
|
|
|
|
s3db = current.s3db
|
|
tablename = "org_sector"
|
|
|
|
# Just 1 set of sectors / sector leads nationally
|
|
# @ToDo: Deployment Setting
|
|
#f = s3db.org_sector.location_id
|
|
#f.readable = f.writable = False
|
|
|
|
# Custom Component for Sector Leads
|
|
s3db.add_components(tablename,
|
|
org_sector_organisation = {"name": "sector_lead",
|
|
"joinby": "sector_id",
|
|
"filterby": {"lead": True,
|
|
},
|
|
},
|
|
)
|
|
|
|
from s3 import S3SQLCustomForm, S3SQLInlineComponent
|
|
crud_form = S3SQLCustomForm("name",
|
|
"abrv",
|
|
"comments",
|
|
S3SQLInlineComponent("sector_lead",
|
|
label = T("Lead Organization(s)"),
|
|
fields = [("", "organisation_id"),],
|
|
),
|
|
)
|
|
|
|
s3db.configure(tablename,
|
|
crud_form = crud_form,
|
|
list_fields = ["name",
|
|
"abrv",
|
|
(T("Lead Organization(s)"), "sector_lead.organisation_id"),
|
|
],
|
|
)
|
|
|
|
return attr
|
|
|
|
settings.customise_org_sector_controller = customise_org_sector_controller
|
|
|
|
# -------------------------------------------------------------------------
|
|
def customise_pr_forum_controller(**attr):
|
|
|
|
s3db = current.s3db
|
|
s3 = current.response.s3
|
|
|
|
s3db.pr_forum
|
|
s3.crud_strings["pr_forum"].title_display = T("HCT Coordination Folders")
|
|
s3.dl_no_header = True
|
|
|
|
# Comments
|
|
appname = current.request.application
|
|
s3.scripts.append("/%s/static/themes/WACOP/js/update_comments.js" % appname)
|
|
script = '''S3.wacop_comments()
|
|
S3.redraw_fns.push('wacop_comments')'''
|
|
s3.jquery_ready.append(script)
|
|
|
|
# Tags for Updates
|
|
if s3.debug:
|
|
s3.scripts.append("/%s/static/scripts/tag-it.js" % appname)
|
|
else:
|
|
s3.scripts.append("/%s/static/scripts/tag-it.min.js" % appname)
|
|
if current.auth.s3_has_permission("update", s3db.cms_tag_post):
|
|
# @ToDo: Move the ajaxUpdateOptions into callback of getS3?
|
|
readonly = '''afterTagAdded:function(event,ui){
|
|
if(ui.duringInitialization){return}
|
|
var post_id=$(this).attr('data-post_id')
|
|
var url=S3.Ap.concat('/cms/post/',post_id,'/add_tag/',ui.tagLabel)
|
|
$.getS3(url)
|
|
S3.search.ajaxUpdateOptions('#datalist-filter-form')
|
|
},afterTagRemoved:function(event,ui){
|
|
var post_id=$(this).attr('data-post_id')
|
|
var url=S3.Ap.concat('/cms/post/',post_id,'/remove_tag/',ui.tagLabel)
|
|
$.getS3(url)
|
|
S3.search.ajaxUpdateOptions('#datalist-filter-form')
|
|
},'''
|
|
else:
|
|
readonly = '''readOnly:true'''
|
|
script = \
|
|
'''S3.tagit=function(){$('.s3-tags').tagit({placeholderText:'%s',autocomplete:{source:'%s'},%s})}
|
|
S3.tagit()
|
|
S3.redraw_fns.push('tagit')''' % (T("Add tags here…"),
|
|
URL(c="cms", f="tag",
|
|
args="tag_list.json"),
|
|
readonly)
|
|
s3.jquery_ready.append(script)
|
|
|
|
attr["rheader"] = None
|
|
attr["hide_filter"] = False
|
|
|
|
return attr
|
|
|
|
settings.customise_pr_forum_controller = customise_pr_forum_controller
|
|
|
|
# -------------------------------------------------------------------------
|
|
def req_need_commit(r, **attr):
|
|
"""
|
|
Custom method to Commit to a Need by creating an Activity Group
|
|
"""
|
|
|
|
# Create Activity Group (Response) with values from Need
|
|
need_id = r.id
|
|
|
|
db = current.db
|
|
s3db = current.s3db
|
|
|
|
ntable = s3db.req_need
|
|
ntable_id = ntable.id
|
|
netable = s3db.event_event_need
|
|
left = [netable.on(netable.need_id == ntable_id),
|
|
]
|
|
need = db(ntable_id == need_id).select(ntable.name,
|
|
ntable.location_id,
|
|
netable.event_id,
|
|
left = left,
|
|
limitby = (0, 1)
|
|
).first()
|
|
|
|
nttable = s3db.req_need_tag
|
|
query = (nttable.need_id == need_id) & \
|
|
(nttable.tag.belongs(("address", "contact"))) & \
|
|
(nttable.deleted == False)
|
|
tags = db(query).select(nttable.tag,
|
|
nttable.value,
|
|
)
|
|
contact = address = None
|
|
for tag in tags:
|
|
if tag.tag == "address":
|
|
address = tag.value
|
|
elif tag.tag == "contact":
|
|
contact = tag.value
|
|
|
|
nrtable = s3db.req_need_response
|
|
need_response_id = nrtable.insert(need_id = need_id,
|
|
name = need["req_need.name"],
|
|
location_id = need["req_need.location_id"],
|
|
contact = contact,
|
|
address = address,
|
|
)
|
|
organisation_id = current.auth.user.organisation_id
|
|
if organisation_id:
|
|
s3db.req_need_response_organisation.insert(need_response_id = need_response_id,
|
|
organisation_id = organisation_id,
|
|
role = 1,
|
|
)
|
|
|
|
event_id = need["event_event_need.event_id"]
|
|
if event_id:
|
|
aetable = s3db.event_event_need_response
|
|
aetable.insert(need_response_id = need_response_id,
|
|
event_id = event_id,
|
|
)
|
|
|
|
nltable = s3db.req_need_line
|
|
query = (nltable.need_id == need_id) & \
|
|
(nltable.deleted == False)
|
|
lines = db(query).select(nltable.id,
|
|
nltable.coarse_location_id,
|
|
nltable.location_id,
|
|
nltable.sector_id,
|
|
nltable.parameter_id,
|
|
nltable.value,
|
|
nltable.value_uncommitted,
|
|
nltable.item_category_id,
|
|
nltable.item_id,
|
|
nltable.item_pack_id,
|
|
nltable.quantity,
|
|
nltable.quantity_uncommitted,
|
|
nltable.status,
|
|
)
|
|
if lines:
|
|
linsert = s3db.req_need_response_line.insert
|
|
for line in lines:
|
|
value_uncommitted = line.value_uncommitted
|
|
if value_uncommitted is None:
|
|
# No commitments yet so commit to all
|
|
value = line.value
|
|
else:
|
|
# Only commit to the remainder
|
|
value = value_uncommitted
|
|
quantity_uncommitted = line.quantity_uncommitted
|
|
if quantity_uncommitted is None:
|
|
# No commitments yet so commit to all
|
|
quantity = line.quantity
|
|
else:
|
|
# Only commit to the remainder
|
|
quantity = quantity_uncommitted
|
|
need_line_id = line.id
|
|
linsert(need_response_id = need_response_id,
|
|
need_line_id = need_line_id,
|
|
coarse_location_id = line.coarse_location_id,
|
|
location_id = line.location_id,
|
|
sector_id = line.sector_id,
|
|
parameter_id = line.parameter_id,
|
|
value = value,
|
|
item_category_id = line.item_category_id,
|
|
item_id = line.item_id,
|
|
item_pack_id = line.item_pack_id,
|
|
quantity = quantity,
|
|
)
|
|
# Update Need Line status
|
|
req_need_line_status_update(need_line_id)
|
|
|
|
# Redirect to Update
|
|
from gluon import redirect
|
|
redirect(URL(c= "req", f="need_response",
|
|
args = [need_response_id, "update"],
|
|
))
|
|
|
|
# -------------------------------------------------------------------------
|
|
def req_need_line_commit(r, **attr):
|
|
"""
|
|
Custom method to Commit to a Need Line by creating an Activity
|
|
"""
|
|
|
|
# Create Activity with values from Need Line
|
|
need_line_id = r.id
|
|
|
|
db = current.db
|
|
s3db = current.s3db
|
|
|
|
nltable = s3db.req_need_line
|
|
query = (nltable.id == need_line_id)
|
|
line = db(query).select(nltable.id,
|
|
nltable.need_id,
|
|
nltable.coarse_location_id,
|
|
nltable.location_id,
|
|
nltable.sector_id,
|
|
nltable.parameter_id,
|
|
nltable.value,
|
|
nltable.value_uncommitted,
|
|
nltable.item_category_id,
|
|
nltable.item_id,
|
|
nltable.item_pack_id,
|
|
nltable.quantity,
|
|
nltable.quantity_uncommitted,
|
|
nltable.status,
|
|
limitby = (0, 1)
|
|
).first()
|
|
|
|
need_id = line.need_id
|
|
|
|
ntable = s3db.req_need
|
|
ntable_id = ntable.id
|
|
netable = s3db.event_event_need
|
|
left = [netable.on(netable.need_id == ntable_id),
|
|
]
|
|
need = db(ntable_id == need_id).select(ntable.name,
|
|
ntable.location_id,
|
|
netable.event_id,
|
|
left = left,
|
|
limitby = (0, 1)
|
|
).first()
|
|
|
|
nttable = s3db.req_need_tag
|
|
query = (nttable.need_id == need_id) & \
|
|
(nttable.tag.belongs(("address", "contact"))) & \
|
|
(nttable.deleted == False)
|
|
tags = db(query).select(nttable.tag,
|
|
nttable.value,
|
|
)
|
|
contact = address = None
|
|
for tag in tags:
|
|
if tag.tag == "address":
|
|
address = tag.value
|
|
elif tag.tag == "contact":
|
|
contact = tag.value
|
|
|
|
nrtable = s3db.req_need_response
|
|
need_response_id = nrtable.insert(need_id = need_id,
|
|
name = need["req_need.name"],
|
|
location_id = need["req_need.location_id"],
|
|
contact = contact,
|
|
address = address,
|
|
)
|
|
organisation_id = current.auth.user.organisation_id
|
|
if organisation_id:
|
|
s3db.req_need_response_organisation.insert(need_response_id = need_response_id,
|
|
organisation_id = organisation_id,
|
|
role = 1,
|
|
)
|
|
|
|
event_id = need["event_event_need.event_id"]
|
|
if event_id:
|
|
aetable = s3db.event_event_need_response
|
|
aetable.insert(need_response_id = need_response_id,
|
|
event_id = event_id,
|
|
)
|
|
|
|
value_uncommitted = line.value_uncommitted
|
|
if value_uncommitted is None:
|
|
# No commitments yet so commit to all
|
|
value = line.value
|
|
else:
|
|
# Only commit to the remainder
|
|
value = value_uncommitted
|
|
quantity_uncommitted = line.quantity_uncommitted
|
|
if quantity_uncommitted is None:
|
|
# No commitments yet so commit to all
|
|
quantity = line.quantity
|
|
else:
|
|
# Only commit to the remainder
|
|
quantity = quantity_uncommitted
|
|
|
|
s3db.req_need_response_line.insert(need_response_id = need_response_id,
|
|
need_line_id = need_line_id,
|
|
coarse_location_id = line.coarse_location_id,
|
|
location_id = line.location_id,
|
|
sector_id = line.sector_id,
|
|
parameter_id = line.parameter_id,
|
|
value = value,
|
|
item_category_id = line.item_category_id,
|
|
item_id = line.item_id,
|
|
item_pack_id = line.item_pack_id,
|
|
quantity = quantity,
|
|
)
|
|
|
|
# Update Need Line status
|
|
req_need_line_status_update(need_line_id)
|
|
|
|
# Redirect to Update
|
|
from gluon import redirect
|
|
redirect(URL(c= "req", f="need_response",
|
|
args = [need_response_id, "update"],
|
|
))
|
|
|
|
# -------------------------------------------------------------------------
|
|
def req_need_line_status_update(need_line_id):
|
|
"""
|
|
Update the Need Line's fulfilment Status
|
|
"""
|
|
|
|
db = current.db
|
|
s3db = current.s3db
|
|
|
|
# Read the Line details
|
|
nltable = s3db.req_need_line
|
|
iptable = s3db.supply_item_pack
|
|
query = (nltable.id == need_line_id)
|
|
left = iptable.on(nltable.item_pack_id == iptable.id)
|
|
need_line = db(query).select(nltable.parameter_id,
|
|
nltable.value,
|
|
nltable.item_id,
|
|
nltable.quantity,
|
|
iptable.quantity,
|
|
left = left,
|
|
limitby = (0, 1)
|
|
).first()
|
|
need_pack_qty = need_line["supply_item_pack.quantity"]
|
|
need_line = need_line["req_need_line"]
|
|
need_parameter_id = need_line.parameter_id
|
|
need_value = need_line.value or 0
|
|
need_value_committed = 0
|
|
need_value_reached = 0
|
|
need_quantity = need_line.quantity
|
|
if need_quantity:
|
|
need_quantity = need_quantity * need_pack_qty
|
|
else:
|
|
need_quantity = 0
|
|
need_item_id = need_line.item_id
|
|
need_quantity_committed = 0
|
|
need_quantity_delivered = 0
|
|
|
|
# Lookup which Status means 'Cancelled'
|
|
stable = s3db.project_status
|
|
status = db(stable.name == "Cancelled").select(stable.id,
|
|
limitby = (0, 1)
|
|
).first()
|
|
try:
|
|
CANCELLED = status.id
|
|
except AttributeError:
|
|
# Prepop not done? Name changed?
|
|
current.log.debug("'Cancelled' Status not found")
|
|
CANCELLED = 999999
|
|
|
|
# Read the details of all Response Lines linked to this Need Line
|
|
rltable = s3db.req_need_response_line
|
|
iptable = s3db.supply_item_pack
|
|
query = (rltable.need_line_id == need_line_id) & \
|
|
(rltable.deleted == False)
|
|
left = iptable.on(rltable.item_pack_id == iptable.id)
|
|
response_lines = db(query).select(rltable.id,
|
|
rltable.parameter_id,
|
|
rltable.value,
|
|
rltable.value_reached,
|
|
rltable.item_id,
|
|
iptable.quantity,
|
|
rltable.quantity,
|
|
rltable.quantity_delivered,
|
|
rltable.status_id,
|
|
left = left,
|
|
)
|
|
for line in response_lines:
|
|
pack_qty = line["supply_item_pack.quantity"]
|
|
line = line["req_need_response_line"]
|
|
if line.status_id == CANCELLED:
|
|
continue
|
|
if line.parameter_id == need_parameter_id:
|
|
value = line.value
|
|
if value:
|
|
need_value_committed += value
|
|
value_reached = line.value_reached
|
|
if value_reached:
|
|
need_value_reached += value_reached
|
|
if line.item_id == need_item_id:
|
|
quantity = line.quantity
|
|
if quantity:
|
|
need_quantity_committed += quantity * pack_qty
|
|
quantity_delivered = line.quantity_delivered
|
|
if quantity_delivered:
|
|
need_quantity_delivered += quantity_delivered * pack_qty
|
|
|
|
# Calculate Need values & Update
|
|
value_uncommitted = max(need_value - need_value_committed, 0)
|
|
quantity_uncommitted = max(need_quantity - need_quantity_committed, 0)
|
|
if (need_quantity_delivered >= need_quantity) and (need_value_reached >= need_value):
|
|
status = 3
|
|
elif (quantity_uncommitted <= 0) and (value_uncommitted <= 0):
|
|
status = 2
|
|
elif (need_quantity_committed > 0) or (need_value_committed > 0):
|
|
status = 1
|
|
else:
|
|
status = 0
|
|
|
|
db(nltable.id == need_line_id).update(value_committed = need_value_committed,
|
|
value_uncommitted = value_uncommitted,
|
|
value_reached = need_value_reached,
|
|
quantity_committed = need_quantity_committed,
|
|
quantity_uncommitted = quantity_uncommitted,
|
|
quantity_delivered = need_quantity_delivered,
|
|
status = status,
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
def req_need_postprocess(form):
|
|
"""
|
|
Set the Realm
|
|
Set the Request Number
|
|
"""
|
|
|
|
need_id = form.vars.id
|
|
|
|
db = current.db
|
|
s3db = current.s3db
|
|
|
|
# Lookup Organisation
|
|
notable = s3db.req_need_organisation
|
|
org_link = db(notable.need_id == need_id).select(notable.organisation_id,
|
|
limitby = (0, 1),
|
|
).first()
|
|
if org_link:
|
|
organisation_id = org_link.organisation_id
|
|
else:
|
|
# Create the link (form isn't doing so when readonly!)
|
|
user = current.auth.user
|
|
if user and user.organisation_id:
|
|
organisation_id = user.organisation_id
|
|
if organisation_id:
|
|
notable.insert(need_id = need_id,
|
|
organisation_id = organisation_id)
|
|
else:
|
|
# Nothing we can do!
|
|
return
|
|
else:
|
|
# Nothing we can do!
|
|
return
|
|
|
|
# Lookup Realm
|
|
otable = s3db.org_organisation
|
|
org = db(otable.id == organisation_id).select(otable.pe_id,
|
|
limitby = (0, 1),
|
|
).first()
|
|
realm_entity = org.pe_id
|
|
|
|
# Set Realm
|
|
ntable = s3db.req_need
|
|
db(ntable.id == need_id).update(realm_entity = realm_entity)
|
|
nltable = s3db.req_need_line
|
|
db(nltable.need_id == need_id).update(realm_entity = realm_entity)
|
|
|
|
if form.record:
|
|
# Update form
|
|
return
|
|
|
|
# Lookup Request Number format
|
|
ottable = s3db.org_organisation_tag
|
|
query = (ottable.organisation_id == organisation_id) & \
|
|
(ottable.tag == "req_number")
|
|
tag = db(query).select(ottable.value,
|
|
limitby = (0, 1),
|
|
).first()
|
|
if not tag:
|
|
return
|
|
|
|
# Lookup most recently-used value
|
|
nttable = s3db.req_need_tag
|
|
query = (nttable.tag == "req_number") & \
|
|
(nttable.need_id != need_id) & \
|
|
(nttable.need_id == notable.need_id) & \
|
|
(notable.organisation_id == organisation_id)
|
|
|
|
need = db(query).select(nttable.value,
|
|
limitby = (0, 1),
|
|
orderby = ~nttable.created_on,
|
|
).first()
|
|
|
|
# Set Request Number
|
|
if need:
|
|
new_number = int(need.value.split("-", 1)[1]) + 1
|
|
req_number = "%s-%s" % (tag.value, str(new_number).zfill(6))
|
|
else:
|
|
req_number = "%s-000001" % tag.value
|
|
|
|
nttable.insert(need_id = need_id,
|
|
tag = "req_number",
|
|
value = req_number,
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
def customise_req_need_resource(r, tablename):
|
|
|
|
from gluon import IS_EMPTY_OR, IS_IN_SET
|
|
|
|
from s3 import s3_comments_widget, \
|
|
S3LocationSelector, S3LocationDropdownWidget, \
|
|
S3Represent, \
|
|
S3SQLCustomForm, S3SQLInlineComponent, S3SQLInlineLink
|
|
|
|
db = current.db
|
|
s3db = current.s3db
|
|
|
|
table = s3db.req_need
|
|
table.name.widget = lambda f, v: \
|
|
s3_comments_widget(f, v, _placeholder = "e.g. 400 families require drinking water in Kegalle DS Division in 1-2 days.")
|
|
|
|
table.comments.comment = None
|
|
table.comments.widget = lambda f, v: \
|
|
s3_comments_widget(f, v, _placeholder = "e.g. Accessibility issues, additional contacts on the ground (if any), any other relevant information.")
|
|
|
|
# These levels/labels are for SHARE/LK
|
|
table.location_id.widget = S3LocationSelector(hide_lx = False,
|
|
levels = ("L1", "L2"),
|
|
required_levels = ("L1", "L2"),
|
|
show_map = False)
|
|
|
|
ltable = s3db.req_need_line
|
|
f = ltable.coarse_location_id
|
|
f.label = T("Division")
|
|
# @ToDo: Option for gis_LocationRepresent which doesn't show level/parent, but supports translation
|
|
# NB cannot have the JS in link to avoid being blocked by Chrome XSS_AUDITOR
|
|
location_represent = S3Represent(lookup = "gis_location")
|
|
f.represent = location_represent
|
|
f.widget = S3LocationDropdownWidget(level="L3", blank=True)
|
|
f = ltable.location_id
|
|
f.label = T("GN")
|
|
f.represent = location_represent
|
|
f.widget = S3LocationDropdownWidget(level="L4", blank=True)
|
|
|
|
# Custom Filtered Components
|
|
s3db.add_components(tablename,
|
|
req_need_tag = (# Address
|
|
{"name": "address",
|
|
"joinby": "need_id",
|
|
"filterby": {"tag": "address",
|
|
},
|
|
"multiple": False,
|
|
},
|
|
# Contact
|
|
{"name": "contact",
|
|
"joinby": "need_id",
|
|
"filterby": {"tag": "contact",
|
|
},
|
|
"multiple": False,
|
|
},
|
|
# Issue
|
|
{"name": "issue",
|
|
"joinby": "need_id",
|
|
"filterby": {"tag": "issue",
|
|
},
|
|
"multiple": False,
|
|
},
|
|
# Req Number
|
|
{"name": "req_number",
|
|
"joinby": "need_id",
|
|
"filterby": {"tag": "req_number",
|
|
},
|
|
"multiple": False,
|
|
},
|
|
# Original Request From
|
|
{"name": "request_from",
|
|
"joinby": "need_id",
|
|
"filterby": {"tag": "request_from",
|
|
},
|
|
"multiple": False,
|
|
},
|
|
# Verified
|
|
{"name": "verified",
|
|
"joinby": "need_id",
|
|
"filterby": {"tag": "verified",
|
|
},
|
|
"multiple": False,
|
|
},
|
|
)
|
|
)
|
|
|
|
# Individual settings for specific tag components
|
|
components_get = s3db.resource(tablename).components.get
|
|
|
|
address = components_get("address")
|
|
f = address.table.value
|
|
f.widget = s3_comments_widget
|
|
|
|
contact = components_get("contact")
|
|
f = contact.table.value
|
|
f.widget = lambda f, v: \
|
|
s3_comments_widget(f, v, _placeholder = "of person on the ground e.g. GA, DS")
|
|
|
|
issue = components_get("issue")
|
|
f = issue.table.value
|
|
f.widget = lambda f, v: \
|
|
s3_comments_widget(f, v, _placeholder = "e.g. Lack of accessibility and contaminated wells due to heavy rainfall.")
|
|
|
|
request_from = components_get("request_from")
|
|
f = request_from.table.value
|
|
f.widget = lambda f, v: \
|
|
s3_comments_widget(f, v, _placeholder = "Please indicate the requesting organisation/ministry.")
|
|
|
|
verified = components_get("verified")
|
|
f = verified.table.value
|
|
f.requires = IS_EMPTY_OR(IS_IN_SET(("Y", "N")))
|
|
f.represent = lambda v: T("yes") if v == "Y" else T("no")
|
|
from s3 import S3TagCheckboxWidget
|
|
f.widget = S3TagCheckboxWidget(on="Y", off="N")
|
|
f.default = "N"
|
|
|
|
auth = current.auth
|
|
user = auth.user
|
|
if user and user.organisation_id:
|
|
organisation_id = user.organisation_id
|
|
else:
|
|
organisation_id = None
|
|
if auth.s3_has_role("ADMIN") or organisation_id:
|
|
f.default = "Y"
|
|
else:
|
|
f.writable = False
|
|
|
|
if r.id and r.resource.tablename == tablename:
|
|
# Read or Update
|
|
create = False
|
|
else:
|
|
# Create
|
|
create = True
|
|
|
|
if not create:
|
|
# Read or Update
|
|
if organisation_id:
|
|
org_readonly = True
|
|
else:
|
|
rotable = s3db.req_need_organisation
|
|
org_link = db(rotable.need_id == r.id).select(rotable.organisation_id,
|
|
limitby = (0, 1)
|
|
).first()
|
|
if org_link:
|
|
org_readonly = True
|
|
else:
|
|
org_readonly = False
|
|
#table = s3db.req_need_item
|
|
#table.quantity.label = T("Quantity Requested")
|
|
#table.quantity_committed.readable = True
|
|
#table.quantity_uncommitted.readable = True
|
|
#table.quantity_delivered.readable = True
|
|
#need_item = S3SQLInlineComponent("need_item",
|
|
# label = T("Items Needed"),
|
|
# fields = ["item_category_id",
|
|
# "item_id",
|
|
# (T("Unit"), "item_pack_id"),
|
|
# (T("Needed within Timeframe"), "timeframe"),
|
|
# "quantity",
|
|
# "quantity_committed",
|
|
# "quantity_uncommitted",
|
|
# "quantity_delivered",
|
|
# #(T("Urgency"), "priority"),
|
|
# "comments",
|
|
# ],
|
|
# )
|
|
#table = s3db.req_need_demographic
|
|
#table.value.label = T("Number in Need")
|
|
#table.value_committed.readable = True
|
|
#table.value_uncommitted.readable = True
|
|
#table.value_reached.readable = True
|
|
#demographic = S3SQLInlineComponent("need_demographic",
|
|
# label = T("People Affected"),
|
|
# fields = [(T("Type"), "parameter_id"),
|
|
# #(T("Needed within Timeframe"), "timeframe"),
|
|
# "value",
|
|
# "value_committed",
|
|
# "value_uncommitted",
|
|
# "value_reached",
|
|
# "comments",
|
|
# ],
|
|
# )
|
|
#ltable.value.label = T("Number in Need")
|
|
ltable.value_committed.readable = True
|
|
ltable.value_uncommitted.readable = True
|
|
ltable.value_reached.readable = True
|
|
#ltable.quantity.label = T("Quantity Requested")
|
|
ltable.quantity_committed.readable = True
|
|
ltable.quantity_uncommitted.readable = True
|
|
ltable.quantity_delivered.readable = True
|
|
line = S3SQLInlineComponent("need_line",
|
|
label = "",
|
|
fields = ["coarse_location_id",
|
|
"location_id",
|
|
"sector_id",
|
|
(T("People affected"), "parameter_id"),
|
|
"value",
|
|
"value_committed",
|
|
(T("Number Outstanding"), "value_uncommitted"),
|
|
"value_reached",
|
|
(T("Item Category"), "item_category_id"),
|
|
"item_id",
|
|
(T("Unit"), "item_pack_id"),
|
|
(T("Item Quantity"), "quantity"),
|
|
(T("Needed within Timeframe"), "timeframe"),
|
|
"quantity_committed",
|
|
(T("Quantity Outstanding"), "quantity_uncommitted"),
|
|
"quantity_delivered",
|
|
#"comments",
|
|
],
|
|
)
|
|
else:
|
|
# Create
|
|
org_readonly = organisation_id is not None
|
|
#need_item = S3SQLInlineComponent("need_item",
|
|
# label = T("Items Needed"),
|
|
# fields = ["item_category_id",
|
|
# "item_id",
|
|
# (T("Unit"), "item_pack_id"),
|
|
# (T("Needed within Timeframe"), "timeframe"),
|
|
# "quantity",
|
|
# #(T("Urgency"), "priority"),
|
|
# "comments",
|
|
# ],
|
|
# )
|
|
#demographic = S3SQLInlineComponent("need_demographic",
|
|
# label = T("People Affected"),
|
|
# fields = [(T("Type"), "parameter_id"),
|
|
# #(T("Needed within Timeframe"), "timeframe"),
|
|
# "value",
|
|
# "comments",
|
|
# ],
|
|
# )
|
|
line = S3SQLInlineComponent("need_line",
|
|
label = "",
|
|
fields = ["coarse_location_id",
|
|
"location_id",
|
|
"sector_id",
|
|
(T("People affected"), "parameter_id"),
|
|
"value",
|
|
(T("Item Category"), "item_category_id"),
|
|
"item_id",
|
|
(T("Unit"), "item_pack_id"),
|
|
"quantity",
|
|
(T("Needed within Timeframe"), "timeframe"),
|
|
#"comments",
|
|
],
|
|
)
|
|
|
|
crud_fields = [S3SQLInlineLink("event",
|
|
field = "event_id",
|
|
label = T("Disaster"),
|
|
multiple = False,
|
|
required = True,
|
|
),
|
|
S3SQLInlineLink("organisation",
|
|
field = "organisation_id",
|
|
search = False,
|
|
label = T("Organization"),
|
|
multiple = False,
|
|
readonly = org_readonly,
|
|
required = not org_readonly,
|
|
),
|
|
"location_id",
|
|
(T("Date entered"), "date"),
|
|
#(T("Urgency"), "priority"),
|
|
# Moved into Lines
|
|
#S3SQLInlineLink("sector",
|
|
# field = "sector_id",
|
|
# search = False,
|
|
# label = T("Sector"),
|
|
# multiple = False,
|
|
# ),
|
|
"name",
|
|
(T("Original Request From"), "request_from.value"),
|
|
(T("Issue/cause"), "issue.value"),
|
|
#demographic,
|
|
#need_item,
|
|
line,
|
|
S3SQLInlineComponent("document",
|
|
label = T("Attachment"),
|
|
fields = [("", "file")],
|
|
# multiple = True has reliability issues in at least Chrome
|
|
multiple = False,
|
|
),
|
|
(T("Verified by government official"), "verified.value"),
|
|
(T("Contact details"), "contact.value"),
|
|
(T("Address for delivery/affected people"), "address.value"),
|
|
"comments",
|
|
]
|
|
|
|
from .controllers import project_ActivityRepresent
|
|
natable = s3db.req_need_activity
|
|
#f = natable.activity_id
|
|
#f.represent = project_ActivityRepresent()
|
|
natable.activity_id.represent = project_ActivityRepresent()
|
|
|
|
if not create:
|
|
# Read or Update
|
|
req_number = components_get("req_number")
|
|
req_number.table.value.writable = False
|
|
crud_fields.insert(2, (T("Request Number"), "req_number.value"))
|
|
crud_fields.insert(-2, "status")
|
|
need_links = db(natable.need_id == r.id).select(natable.activity_id)
|
|
if need_links:
|
|
# This hides the widget from Update forms instead of just rendering read-only!
|
|
#f.writable = False
|
|
crud_fields.append(S3SQLInlineLink("activity",
|
|
field = "activity_id",
|
|
label = T("Commits"),
|
|
readonly = True,
|
|
))
|
|
|
|
crud_form = S3SQLCustomForm(*crud_fields,
|
|
postprocess = req_need_postprocess)
|
|
|
|
need_line_summary = URL(c="req", f="need_line", args="summary")
|
|
|
|
s3db.configure(tablename,
|
|
create_next = need_line_summary,
|
|
delete_next = need_line_summary,
|
|
update_next = need_line_summary,
|
|
crud_form = crud_form,
|
|
)
|
|
|
|
settings.customise_req_need_resource = customise_req_need_resource
|
|
|
|
# -------------------------------------------------------------------------
|
|
def req_need_rheader(r):
|
|
"""
|
|
Resource Header for Needs
|
|
"""
|
|
|
|
if r.representation != "html":
|
|
# RHeaders only used in interactive views
|
|
return None
|
|
|
|
record = r.record
|
|
if not record:
|
|
# RHeaders only used in single-record views
|
|
return None
|
|
|
|
if r.name == "need":
|
|
# No Tabs (all done Inline)
|
|
tabs = [(T("Basic Details"), None),
|
|
#(T("Demographics"), "demographic"),
|
|
#(T("Items"), "need_item"),
|
|
#(T("Skills"), "need_skill"),
|
|
#(T("Tags"), "tag"),
|
|
]
|
|
|
|
from s3 import s3_rheader_tabs
|
|
rheader_tabs = s3_rheader_tabs(r, tabs)
|
|
|
|
location_id = r.table.location_id
|
|
from gluon import DIV, TABLE, TR, TH
|
|
rheader = DIV(TABLE(TR(TH("%s: " % location_id.label),
|
|
location_id.represent(record.location_id),
|
|
)),
|
|
rheader_tabs)
|
|
|
|
else:
|
|
# Not defined, probably using wrong rheader
|
|
rheader = None
|
|
|
|
return rheader
|
|
|
|
# -------------------------------------------------------------------------
|
|
def customise_req_need_controller(**attr):
|
|
|
|
line_id = current.request.get_vars.get("line")
|
|
if line_id:
|
|
from gluon import redirect
|
|
nltable = current.s3db.req_need_line
|
|
line = current.db(nltable.id == line_id).select(nltable.need_id,
|
|
limitby = (0, 1)
|
|
).first()
|
|
if line:
|
|
redirect(URL(args = [line.need_id],
|
|
vars = {}))
|
|
|
|
# Custom commit method to create an Activity Group from a Need
|
|
current.s3db.set_method("req", "need",
|
|
method = "commit",
|
|
action = req_need_commit)
|
|
|
|
s3 = current.response.s3
|
|
|
|
# Custom postp
|
|
standard_postp = s3.postp
|
|
def postp(r, output):
|
|
# Call standard postp
|
|
if callable(standard_postp):
|
|
output = standard_postp(r, output)
|
|
|
|
if r.interactive:
|
|
# Inject the javascript to handle dropdown filtering
|
|
# - normally injected through AddResourceLink, but this isn't there in Inline widget
|
|
# - we also need to turn the trigger & target into dicts
|
|
s3.scripts.append("/%s/static/themes/SHARE/js/need.js" % r.application)
|
|
|
|
if r.id and isinstance(output, dict) and \
|
|
current.auth.s3_has_permission("create", "project_activity"):
|
|
# Custom Button
|
|
from gluon import A
|
|
output["commit"] = A(T("Commit"),
|
|
_href = URL(args=[r.id, "commit"]),
|
|
_class = "action-btn",
|
|
#_id = "commit-btn",
|
|
)
|
|
#s3.jquery_ready.append(
|
|
#'''S3.confirmClick('#commit-btn','%s')''' % T("Do you want to commit to this need?"))
|
|
|
|
return output
|
|
s3.postp = postp
|
|
|
|
attr["rheader"] = req_need_rheader
|
|
|
|
return attr
|
|
|
|
settings.customise_req_need_controller = customise_req_need_controller
|
|
|
|
# -------------------------------------------------------------------------
|
|
def homepage_stats_update():
|
|
"""
|
|
Scheduler task to update the data files for the charts
|
|
on the homepage
|
|
"""
|
|
|
|
from .controllers import HomepageStatistics
|
|
HomepageStatistics.update_data()
|
|
|
|
settings.tasks.homepage_stats_update = homepage_stats_update
|
|
|
|
def req_need_line_update_stats(r, **attr):
|
|
"""
|
|
Method to manually update the data files for the charts
|
|
on the homepage; can be run by POSTing an empty request
|
|
to req/need_line/update_stats, e.g. via:
|
|
|
|
<form action='{{=URL(c="req", f="need_line", args=["update_stats"])}}' method='post'>
|
|
<button type='submit'>{{=T("Update Stats")}}</button>
|
|
</form>
|
|
|
|
(this could e.g. be added to the page footer for ADMINs)
|
|
"""
|
|
|
|
if r.http == "POST":
|
|
|
|
if not current.auth.s3_has_role("ADMIN"):
|
|
# No, this is not open for everybody
|
|
r.unauthorized()
|
|
else:
|
|
current.s3task.run_async("settings_task",
|
|
args = ["homepage_stats_update"])
|
|
current.session.confirmation = T("Statistics data update started")
|
|
|
|
from gluon import redirect
|
|
redirect(URL(c="default", f="index"))
|
|
else:
|
|
r.error("405", current.ERROR.BAD_METHOD)
|
|
|
|
# -------------------------------------------------------------------------
|
|
def customise_req_need_line_resource(r, tablename):
|
|
|
|
from gluon import IS_EMPTY_OR, IS_IN_SET, SPAN
|
|
|
|
from s3 import S3Represent
|
|
|
|
s3db = current.s3db
|
|
|
|
current.response.s3.crud_strings["req_need_line"]["title_map"] = T("Map of Needs")
|
|
|
|
req_status_opts = {0: SPAN(T("Uncommitted"),
|
|
_class = "req_status_none",
|
|
),
|
|
1: SPAN(T("Partially Committed"),
|
|
_class = "req_status_partial",
|
|
),
|
|
2: SPAN(T("Fully Committed"),
|
|
_class = "req_status_committed",
|
|
),
|
|
3: SPAN(T("Complete"),
|
|
_class = "req_status_complete",
|
|
),
|
|
}
|
|
|
|
table = s3db.req_need_line
|
|
|
|
f = table.status
|
|
f.requires = IS_EMPTY_OR(IS_IN_SET(req_status_opts, zero = None))
|
|
f.represent = S3Represent(options = req_status_opts)
|
|
|
|
f = table.coarse_location_id
|
|
f.label = T("Division")
|
|
# @ToDo: Option for gis_LocationRepresent which doesn't show level/parent, but supports translation
|
|
# NB cannot have the JS in link to avoid being blocked by Chrome XSS_AUDITOR
|
|
location_represent = S3Represent(lookup = "gis_location")
|
|
f.represent = location_represent
|
|
f = table.location_id
|
|
# @ToDo: Option for gis_LocationRepresent which doesn't show level/parent, but supports translation
|
|
f.represent = location_represent
|
|
|
|
if r.representation == "plain":
|
|
# Settings for Map Popups
|
|
f.label = T("GN")
|
|
|
|
# Custom method to (manually) update homepage statistics
|
|
s3db.set_method("req", "need_line",
|
|
method = "update_stats",
|
|
action = req_need_line_update_stats,
|
|
)
|
|
|
|
settings.customise_req_need_line_resource = customise_req_need_line_resource
|
|
|
|
# -------------------------------------------------------------------------
|
|
def customise_req_need_line_controller(**attr):
|
|
|
|
from s3 import S3OptionsFilter, S3TextFilter #, S3DateFilter, S3LocationFilter
|
|
|
|
s3db = current.s3db
|
|
|
|
settings.base.pdf_orientation = "Landscape"
|
|
|
|
settings.ui.summary = (# Gets replaced in postp
|
|
# @ToDo: better performance by not including here & placing directly into the view instead
|
|
{"common": True,
|
|
"name": "add",
|
|
"widgets": [{"method": "create"}],
|
|
},
|
|
#{"common": True,
|
|
# "name": "cms",
|
|
# "widgets": [{"method": "cms"}],
|
|
# },
|
|
{"name": "table",
|
|
"label": "Table",
|
|
"widgets": [{"method": "datatable"}],
|
|
},
|
|
{"name": "charts",
|
|
"label": "Report",
|
|
"widgets": [{"method": "report",
|
|
"ajax_init": True}],
|
|
},
|
|
#{"name": "map",
|
|
# "label": "Map",
|
|
# "widgets": [{"method": "map",
|
|
# "ajax_init": True}],
|
|
# },
|
|
)
|
|
|
|
# Custom Filtered Components
|
|
s3db.add_components("req_need",
|
|
req_need_tag = (# Req Number
|
|
{"name": "req_number",
|
|
"joinby": "need_id",
|
|
"filterby": {"tag": "req_number",
|
|
},
|
|
"multiple": False,
|
|
},
|
|
# Original Request From
|
|
{"name": "request_from",
|
|
"joinby": "need_id",
|
|
"filterby": {"tag": "request_from",
|
|
},
|
|
"multiple": False,
|
|
},
|
|
# Verified
|
|
{"name": "verified",
|
|
"joinby": "need_id",
|
|
"filterby": {"tag": "verified",
|
|
},
|
|
"multiple": False,
|
|
},
|
|
),
|
|
)
|
|
|
|
s3db.add_components("req_need_response",
|
|
req_need_response_organisation = (# Agency
|
|
{"name": "agency",
|
|
"joinby": "need_response_id",
|
|
"filterby": {"role": 1,
|
|
},
|
|
#"multiple": False,
|
|
},
|
|
),
|
|
)
|
|
|
|
filter_widgets = [S3TextFilter(["need_id$req_number.value",
|
|
"item_id$name",
|
|
# These levels are for SHARE/LK
|
|
#"location_id$L1",
|
|
"location_id$L2",
|
|
#"location_id$L3",
|
|
#"location_id$L4",
|
|
"need_id$name",
|
|
"need_id$comments",
|
|
],
|
|
label = T("Search"),
|
|
comment = T("Search for a Need by Request Number, Item, Location, Summary or Comments"),
|
|
),
|
|
#S3OptionsFilter("need_id$event.event_type_id",
|
|
# #hidden = True,
|
|
# ),
|
|
# @ToDo: Filter this list dynamically based on Event Type (if-used):
|
|
S3OptionsFilter("need_id$event__link.event_id"),
|
|
#S3LocationFilter("location_id",
|
|
# # These levels are for SHARE/LK
|
|
# levels = ("L2", "L3", "L4"),
|
|
# ),
|
|
S3OptionsFilter("need_id$location_id",
|
|
label = T("District"),
|
|
),
|
|
S3OptionsFilter("need_id$organisation__link.organisation_id",
|
|
#hidden = True,
|
|
),
|
|
S3OptionsFilter("sector_id",
|
|
#hidden = True,
|
|
),
|
|
S3OptionsFilter("parameter_id"),
|
|
S3OptionsFilter("timeframe"),
|
|
S3OptionsFilter("item_id"),
|
|
S3OptionsFilter("status",
|
|
cols = 3,
|
|
table = False,
|
|
label = T("Status"),
|
|
),
|
|
#S3DateFilter("date",
|
|
# ),
|
|
#S3OptionsFilter("need_id$verified.value",
|
|
# cols = 2,
|
|
# label = T("Verified"),
|
|
# #hidden = True,
|
|
# ),
|
|
]
|
|
|
|
s3db.configure("req_need_line",
|
|
filter_widgets = filter_widgets,
|
|
# We create a custom Create Button to create a Need not a Need Line
|
|
listadd = False,
|
|
list_fields = [(T("Status"), "status"),
|
|
(T("Orgs responding"), "need_response_line.need_response_id$agency.organisation_id"),
|
|
"need_id$date",
|
|
(T("Need entered by"), "need_id$organisation__link.organisation_id"),
|
|
(T("Original Request From"), "need_id$request_from.value"),
|
|
# These levels/Labels are for SHARE/LK
|
|
#(T("Province"), "need_id$location_id$L1"),
|
|
(T("District"), "need_id$location_id$L2"),
|
|
#(T("DS"), "location_id$L3"),
|
|
#(T("GN"), "location_id$L4"),
|
|
"sector_id",
|
|
"parameter_id",
|
|
"item_id",
|
|
"quantity",
|
|
(T("Quantity Outstanding"),"quantity_uncommitted"),
|
|
"timeframe",
|
|
(T("Request Number"), "need_id$req_number.value"),
|
|
],
|
|
popup_url = URL(c="req", f="need",
|
|
vars = {"line": "[id]"}
|
|
),
|
|
)
|
|
|
|
# Custom commit method to create an Activity from a Need Line
|
|
s3db.set_method("req", "need_line",
|
|
method = "commit",
|
|
action = req_need_line_commit)
|
|
|
|
s3 = current.response.s3
|
|
|
|
s3.crud_strings["req_need_line"] = Storage(
|
|
#label_create = T("Add Needs"),
|
|
title_list = T("Needs"),
|
|
#title_display=T("Needs"),
|
|
#title_update=T("Edit Needs"),
|
|
#title_upload = T("Import Needs"),
|
|
#label_list_button = T("List Needs"),
|
|
#label_delete_button=T("Delete Needs"),
|
|
msg_record_created=T("Needs added"),
|
|
msg_record_modified=T("Needs updated"),
|
|
msg_record_deleted=T("Needs deleted"),
|
|
msg_list_empty = T("No Needs currently registered"),
|
|
)
|
|
|
|
# Custom postp
|
|
standard_postp = s3.postp
|
|
def postp(r, output):
|
|
# Call standard postp
|
|
if callable(standard_postp):
|
|
output = standard_postp(r, output)
|
|
|
|
if r.interactive and r.method == "summary":
|
|
|
|
from gluon import A, DIV
|
|
from s3 import s3_str#, S3CRUD
|
|
|
|
auth = current.auth
|
|
|
|
# Normal Action Buttons
|
|
#S3CRUD.action_buttons(r)
|
|
# Custom Action Buttons
|
|
deletable = current.db(auth.s3_accessible_query("delete", "req_need_line")).select(s3db.req_need_line.id)
|
|
restrict_d = [str(row.id) for row in deletable]
|
|
s3.actions = [{"label": s3_str(T("Open")),
|
|
"_class": "action-btn",
|
|
"url": URL(f="need", vars={"line": "[id]"}),
|
|
},
|
|
{"label": s3_str(T("Delete")),
|
|
"_class": "delete-btn",
|
|
"url": URL(args=["[id]", "delete"]),
|
|
"restrict": restrict_d,
|
|
},
|
|
]
|
|
if auth.s3_has_permission("create", "req_need_response"):
|
|
s3.actions.append({"label": s3_str(T("Commit")),
|
|
"_class": "action-btn",
|
|
"url": URL(args=["[id]", "commit"]),
|
|
})
|
|
|
|
# Custom Create Button
|
|
add_btn = DIV(DIV(DIV(A(T("Add Needs"),
|
|
_class = "action-btn",
|
|
_href = URL(f="need", args="create"),
|
|
),
|
|
_id = "list-btn-add",
|
|
),
|
|
_class = "widget-container with-tabs",
|
|
),
|
|
_class = "section-container",
|
|
)
|
|
output["common"][0] = add_btn
|
|
|
|
return output
|
|
s3.postp = postp
|
|
|
|
return attr
|
|
|
|
settings.customise_req_need_line_controller = customise_req_need_line_controller
|
|
|
|
# -------------------------------------------------------------------------
|
|
def req_need_response_postprocess(form):
|
|
"""
|
|
Set the Realm
|
|
Ensure that the Need Lines (if-any) have the correct Status
|
|
"""
|
|
|
|
db = current.db
|
|
s3db = current.s3db
|
|
|
|
need_response_id = form.vars.id
|
|
|
|
# Lookup Organisation
|
|
nrotable = s3db.req_need_response_organisation
|
|
query = (nrotable.need_response_id == need_response_id) & \
|
|
(nrotable.role == 1)
|
|
org_link = db(query).select(nrotable.organisation_id,
|
|
limitby = (0, 1),
|
|
).first()
|
|
if not org_link:
|
|
return
|
|
|
|
organisation_id = org_link.organisation_id
|
|
|
|
# Lookup Realm
|
|
otable = s3db.org_organisation
|
|
org = db(otable.id == organisation_id).select(otable.pe_id,
|
|
limitby = (0, 1),
|
|
).first()
|
|
realm_entity = org.pe_id
|
|
|
|
# Set Realm
|
|
nrtable = s3db.req_need_response
|
|
db(nrtable.id == need_response_id).update(realm_entity = realm_entity)
|
|
rltable = s3db.req_need_response_line
|
|
db(rltable.need_response_id == need_response_id).update(realm_entity = realm_entity)
|
|
|
|
# Lookup the Need Lines
|
|
query = (rltable.need_response_id == need_response_id) & \
|
|
(rltable.deleted == False)
|
|
response_lines = db(query).select(rltable.need_line_id)
|
|
|
|
for line in response_lines:
|
|
need_line_id = line.need_line_id
|
|
if need_line_id:
|
|
req_need_line_status_update(need_line_id)
|
|
|
|
# -------------------------------------------------------------------------
|
|
def customise_req_need_response_resource(r, tablename):
|
|
|
|
from s3 import s3_comments_widget, \
|
|
S3LocationDropdownWidget, S3LocationSelector, \
|
|
S3Represent, \
|
|
S3SQLCustomForm, S3SQLInlineComponent, S3SQLInlineLink
|
|
|
|
#db = current.db
|
|
s3db = current.s3db
|
|
|
|
table = s3db.req_need_response
|
|
|
|
current.response.s3.crud_strings[tablename] = Storage(
|
|
label_create = T("Add Activities"),
|
|
title_list = T("Activities"),
|
|
title_display = T("Activities"),
|
|
title_update = T("Edit Activities"),
|
|
title_upload = T("Import Activities"),
|
|
label_list_button = T("List Activities"),
|
|
label_delete_button = T("Delete Activities"),
|
|
msg_record_created = T("Activities added"),
|
|
msg_record_modified = T("Activities updated"),
|
|
msg_record_deleted = T("Activities deleted"),
|
|
msg_list_empty = T("No Activities currently registered"),
|
|
)
|
|
|
|
# These levels/labels are for SHARE/LK
|
|
table.location_id.widget = S3LocationSelector(hide_lx = False,
|
|
levels = ("L1", "L2"),
|
|
required_levels = ("L1", "L2"),
|
|
show_map = False)
|
|
|
|
ltable = s3db.req_need_response_line
|
|
f = ltable.coarse_location_id
|
|
f.label = T("Division")
|
|
# @ToDo: Option for gis_LocationRepresent which doesn't show level/parent, but supports translation
|
|
f.represent = S3Represent(lookup = "gis_location")
|
|
f.widget = S3LocationDropdownWidget(level="L3", blank=True)
|
|
f = ltable.location_id
|
|
f.label = T("GN")
|
|
# @ToDo: Option for gis_LocationRepresent which doesn't show level/parent, but supports translation
|
|
f.represent = S3Represent(lookup = "gis_location")
|
|
f.widget = S3LocationDropdownWidget(level="L4", blank=True)
|
|
|
|
table.comments.comment = None
|
|
table.comments.widget = lambda f, v: \
|
|
s3_comments_widget(f, v, _placeholder = "e.g. Items changed/replaced within kits, details on partial committments to a need, any other relevant information.")
|
|
|
|
# Custom Filtered Components
|
|
s3db.add_components(tablename,
|
|
req_need_response_organisation = (# Agency
|
|
{"name": "agency",
|
|
"joinby": "need_response_id",
|
|
"filterby": {"role": 1,
|
|
},
|
|
"multiple": False,
|
|
},
|
|
# Partners
|
|
{"name": "partner",
|
|
"joinby": "need_response_id",
|
|
"filterby": {"role": 2,
|
|
},
|
|
#"multiple": False,
|
|
},
|
|
# Donors
|
|
{"name": "donor",
|
|
"joinby": "need_response_id",
|
|
"filterby": {"role": 3,
|
|
},
|
|
#"multiple": False,
|
|
},
|
|
),
|
|
)
|
|
|
|
# Individual settings for specific tag components
|
|
components_get = s3db.resource(tablename).components.get
|
|
|
|
donor = components_get("donor")
|
|
donor.table.organisation_id.default = None
|
|
|
|
partner = components_get("partner")
|
|
partner.table.organisation_id.default = None
|
|
|
|
crud_fields = [S3SQLInlineLink("event",
|
|
field = "event_id",
|
|
label = T("Disaster"),
|
|
multiple = False,
|
|
#required = True,
|
|
),
|
|
S3SQLInlineComponent("agency",
|
|
name = "agency",
|
|
label = T("Organization"),
|
|
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",
|
|
(T("Date entered"), "date"),
|
|
(T("Summary of Needs/Activities"), "name"),
|
|
S3SQLInlineComponent("need_response_line",
|
|
label = "",
|
|
fields = ["coarse_location_id",
|
|
"location_id",
|
|
"sector_id",
|
|
"modality",
|
|
(T("Activity Date Planned"), "date"),
|
|
(T("Activity Date Completed"), "end_date"),
|
|
(T("Beneficiaries (Type)"), "parameter_id"),
|
|
(T("Beneficiaries Planned"), "value"),
|
|
(T("Beneficiaries Reached"), "value_reached"),
|
|
(T("Item Category"), "item_category_id"),
|
|
"item_id",
|
|
(T("Unit"), "item_pack_id"),
|
|
(T("Quantity Planned"), "quantity"),
|
|
(T("Quantity Delivered"), "quantity_delivered"),
|
|
(T("Activity Status"), "status_id"),
|
|
#"comments",
|
|
],
|
|
#multiple = False,
|
|
),
|
|
S3SQLInlineComponent("document",
|
|
label = T("Attachment"),
|
|
fields = [("", "file")],
|
|
# multiple = True has reliability issues in at least Chrome
|
|
multiple = False,
|
|
),
|
|
"contact",
|
|
"address",
|
|
"comments",
|
|
]
|
|
|
|
if r.id and r.resource.tablename == tablename and r.record.need_id:
|
|
from .controllers import req_NeedRepresent
|
|
f = table.need_id
|
|
f.represent = req_NeedRepresent()
|
|
f.writable = False
|
|
crud_fields.insert(7, "need_id")
|
|
|
|
# Post-process to update need status for response line changes
|
|
crud_form = S3SQLCustomForm(*crud_fields,
|
|
postprocess = req_need_response_postprocess)
|
|
# Make sure need status gets also updated when response lines are deleted
|
|
s3db.configure("req_need_response_line",
|
|
ondelete = req_need_response_line_ondelete,
|
|
)
|
|
|
|
need_response_line_summary = URL(c="req", f="need_response_line", args="summary")
|
|
|
|
s3db.configure(tablename,
|
|
crud_form = crud_form,
|
|
create_next = need_response_line_summary,
|
|
delete_next = need_response_line_summary,
|
|
update_next = need_response_line_summary,
|
|
)
|
|
|
|
settings.customise_req_need_response_resource = customise_req_need_response_resource
|
|
|
|
# -------------------------------------------------------------------------
|
|
def customise_req_need_response_controller(**attr):
|
|
|
|
line_id = current.request.get_vars.get("line")
|
|
if line_id:
|
|
from gluon import redirect
|
|
nltable = current.s3db.req_need_response_line
|
|
line = current.db(nltable.id == line_id).select(nltable.need_response_id,
|
|
limitby = (0, 1)
|
|
).first()
|
|
if line:
|
|
redirect(URL(args = [line.need_response_id],
|
|
vars = {}))
|
|
|
|
s3 = current.response.s3
|
|
|
|
# Custom postp
|
|
standard_postp = s3.postp
|
|
def postp(r, output):
|
|
# Call standard postp
|
|
if callable(standard_postp):
|
|
output = standard_postp(r, output)
|
|
|
|
if r.interactive:
|
|
# Inject the javascript to handle dropdown filtering
|
|
# - normally injected through AddResourceLink, but this isn't there in Inline widget
|
|
# - we also need to turn the trigger & target into dicts
|
|
s3.scripts.append("/%s/static/themes/SHARE/js/need_response.js" % r.application)
|
|
|
|
return output
|
|
s3.postp = postp
|
|
|
|
return attr
|
|
|
|
settings.customise_req_need_response_controller = customise_req_need_response_controller
|
|
|
|
# -------------------------------------------------------------------------
|
|
def req_need_response_line_ondelete(row):
|
|
"""
|
|
Ensure that the Need Line (if-any) has the correct Status
|
|
"""
|
|
|
|
import json
|
|
|
|
db = current.db
|
|
s3db = current.s3db
|
|
|
|
response_line_id = row.get("id")
|
|
|
|
# Lookup the Need Line
|
|
rltable = s3db.req_need_response_line
|
|
record = db(rltable.id == response_line_id).select(rltable.deleted_fk,
|
|
limitby = (0, 1)
|
|
).first()
|
|
if not record:
|
|
return
|
|
|
|
deleted_fk = json.loads(record.deleted_fk)
|
|
need_line_id = deleted_fk.get("need_line_id")
|
|
|
|
if not need_line_id:
|
|
return
|
|
|
|
# Check that the Need Line hasn't been deleted
|
|
nltable = s3db.req_need_line
|
|
need_line = db(nltable.id == need_line_id).select(nltable.deleted,
|
|
limitby = (0, 1)
|
|
).first()
|
|
|
|
if need_line and not need_line.deleted:
|
|
req_need_line_status_update(need_line_id)
|
|
|
|
# -------------------------------------------------------------------------
|
|
def customise_req_need_response_line_resource(r, tablename):
|
|
|
|
from s3 import S3Represent
|
|
|
|
s3db = current.s3db
|
|
table = s3db.req_need_response_line
|
|
|
|
#current.response.s3.crud_strings["req_need_response_line"] = Storage(title_map = T("Map of Activities"),)
|
|
|
|
# Settings for Map Popups
|
|
f = table.coarse_location_id
|
|
f.label = T("Division")
|
|
# @ToDo: Option for gis_LocationRepresent which doesn't show level/parent, but supports translation
|
|
f.represent = S3Represent(lookup = "gis_location")
|
|
f = table.location_id
|
|
f.label = T("GN")
|
|
# @ToDo: Option for gis_LocationRepresent which doesn't show level/parent, but supports translation
|
|
f.represent = S3Represent(lookup = "gis_location")
|
|
|
|
s3db.configure(tablename,
|
|
ondelete = req_need_response_line_ondelete,
|
|
popup_url = URL(c="req", f="need_response",
|
|
vars = {"line": "[id]"}
|
|
),
|
|
report_represent = NeedResponseLineReportRepresent,
|
|
)
|
|
|
|
settings.customise_req_need_response_line_resource = customise_req_need_response_line_resource
|
|
|
|
# -------------------------------------------------------------------------
|
|
def customise_req_need_response_line_controller(**attr):
|
|
|
|
from s3 import S3OptionsFilter #, S3DateFilter, S3LocationFilter, S3TextFilter
|
|
|
|
s3db = current.s3db
|
|
table = s3db.req_need_response_line
|
|
|
|
settings.base.pdf_orientation = "Landscape"
|
|
|
|
settings.ui.summary = (# Gets replaced in postp
|
|
# @ToDo: better performance by not including here & placing directly into the view instead
|
|
{"common": True,
|
|
"name": "add",
|
|
"widgets": [{"method": "create"}],
|
|
},
|
|
#{"common": True,
|
|
# "name": "cms",
|
|
# "widgets": [{"method": "cms"}],
|
|
# },
|
|
{"name": "table",
|
|
"label": "Table",
|
|
"widgets": [{"method": "datatable"}],
|
|
},
|
|
{"name": "charts",
|
|
"label": "Report",
|
|
"widgets": [{"method": "report",
|
|
"ajax_init": True}],
|
|
},
|
|
#{"name": "map",
|
|
# "label": "Map",
|
|
# "widgets": [{"method": "map",
|
|
# "ajax_init": True}],
|
|
# },
|
|
)
|
|
|
|
# Custom Filtered Components
|
|
s3db.add_components("req_need_response",
|
|
req_need_response_organisation = (# Agency
|
|
{"name": "agency",
|
|
"joinby": "need_response_id",
|
|
"filterby": {"role": 1,
|
|
},
|
|
#"multiple": False,
|
|
},
|
|
# Partners
|
|
{"name": "partner",
|
|
"joinby": "need_response_id",
|
|
"filterby": {"role": 2,
|
|
},
|
|
#"multiple": False,
|
|
},
|
|
# Donors
|
|
{"name": "donor",
|
|
"joinby": "need_response_id",
|
|
"filterby": {"role": 3,
|
|
},
|
|
#"multiple": False,
|
|
},
|
|
),
|
|
)
|
|
|
|
s3 = current.response.s3
|
|
|
|
# Custom prep
|
|
standard_prep = s3.prep
|
|
def prep(r):
|
|
# Call standard prep
|
|
if callable(standard_prep):
|
|
result = standard_postp(r)
|
|
else:
|
|
result = True
|
|
|
|
filter_widgets = [S3OptionsFilter("need_response_id$agency.organisation_id",
|
|
label = T("Organization"),
|
|
),
|
|
#S3OptionsFilter("need_response_id$event.event_type_id",
|
|
# #hidden = True,
|
|
# ),
|
|
# @ToDo: Filter this list dynamically based on Event Type (if-used):
|
|
S3OptionsFilter("need_response_id$event__link.event_id",
|
|
#hidden = True,
|
|
),
|
|
S3OptionsFilter("sector_id"),
|
|
#S3LocationFilter("location_id",
|
|
# label = T("Location"),
|
|
# # These levels are for SHARE/LK
|
|
# levels = ("L2", "L3", "L4"),
|
|
# ),
|
|
S3OptionsFilter("need_response_id$location_id",
|
|
label = T("District"),
|
|
),
|
|
S3OptionsFilter("need_response_id$donor.organisation_id",
|
|
label = T("Donor"),
|
|
),
|
|
S3OptionsFilter("need_response_id$partner.organisation_id",
|
|
label = T("Partner"),
|
|
),
|
|
S3OptionsFilter("parameter_id"),
|
|
S3OptionsFilter("item_id"),
|
|
#S3OptionsFilter("modality"),
|
|
#S3DateFilter("date"),
|
|
S3OptionsFilter("status_id",
|
|
cols = 4,
|
|
label = T("Status"),
|
|
#hidden = True,
|
|
),
|
|
]
|
|
|
|
list_fields = [(T("Organization"), "need_response_id$agency.organisation_id"),
|
|
(T("Implementing Partner"), "need_response_id$partner.organisation_id"),
|
|
(T("Donor"), "need_response_id$donor.organisation_id"),
|
|
# These levels/labels are for SHARE/LK
|
|
#(T("Province"), "need_response_id$location_id$L1"),
|
|
(T("District"), "need_response_id$location_id$L2"),
|
|
"coarse_location_id",
|
|
"location_id",
|
|
(T("Sector"), "sector_id"),
|
|
(T("Item"), "item_id"),
|
|
(T("Items Planned"), "quantity"),
|
|
#(T("Items Delivered"), "quantity_delivered"),
|
|
(T("Modality"), "modality"),
|
|
(T("Beneficiaries Planned"), "value"),
|
|
(T("Beneficiaries Reached"), "value_reached"),
|
|
(T("Activity Date (Planned"), "date"),
|
|
(T("Activity Status"), "status_id"),
|
|
]
|
|
|
|
if r.interactive:
|
|
s3.crud_strings["req_need_response_line"] = Storage(
|
|
#label_create = T("Add Activity"),
|
|
title_list = T("Activities"),
|
|
#title_display = T("Activity"),
|
|
#title_update = T("Edit Activity"),
|
|
#title_upload = T("Import Activities"),
|
|
#label_list_button = T("List Activities"),
|
|
#label_delete_button = T("Delete Activity"),
|
|
#msg_record_created = T("Activity added"),
|
|
#msg_record_modified = T("Activity updated"),
|
|
msg_record_deleted = T("Activity deleted"),
|
|
msg_list_empty = T("No Activities currently registered"),
|
|
)
|
|
|
|
#if r.method == "report":
|
|
# # In report drilldown, include the (Location) after quantity_delivered
|
|
# # => Needs to be a VF as we can't read the record from within represents
|
|
# #table.quantity_delivered.represent =
|
|
#
|
|
# from s3 import S3Represent, s3_fieldmethod
|
|
#
|
|
# # @ToDo: Option for gis_LocationRepresent which doesn't show level/parent, but supports translation
|
|
# gis_represent = S3Represent(lookup = "gis_location")
|
|
#
|
|
# def quantity_delivered_w_location(row):
|
|
# quantity_delivered = row["req_need_response_line.quantity_delivered"]
|
|
# location_id = row["req_need_response_line.location_id"]
|
|
# if not location_id:
|
|
# location_id = row["req_need_response_line.coarse_location_id"]
|
|
# if not location_id:
|
|
# location_id = row["req_need_response.location_id"]
|
|
# location = gis_represent(location_id)
|
|
# return "%s (%s)" % (quantity_delivered, location)
|
|
#
|
|
# table.quantity_delivered_w_location = s3_fieldmethod("quantity_delivered_w_location",
|
|
# quantity_delivered_w_location,
|
|
# # over-ride the default represent of s3_unicode to prevent HTML being rendered too early
|
|
# #represent = lambda v: v,
|
|
# )
|
|
# list_fields.insert(9, (T("Items Delivered"), "quantity_delivered_w_location"))
|
|
#else:
|
|
list_fields.insert(9, (T("Items Delivered"), "quantity_delivered"))
|
|
|
|
# Exclude the Disaster column from PDF exports
|
|
if r.representation != "pdf":
|
|
list_fields.insert(0, (T("Disaster"), "need_response_id$event__link.event_id"))
|
|
|
|
s3db.configure("req_need_response_line",
|
|
filter_widgets = filter_widgets,
|
|
# We create a custom Create Button to create a Need Response not a Need Response Line
|
|
listadd = False,
|
|
list_fields = list_fields,
|
|
)
|
|
|
|
return result
|
|
s3.prep = prep
|
|
|
|
# Custom postp
|
|
standard_postp = s3.postp
|
|
def postp(r, output):
|
|
# Call standard postp
|
|
if callable(standard_postp):
|
|
output = standard_postp(r, output)
|
|
|
|
if r.interactive and r.method == "summary":
|
|
from gluon import A, DIV
|
|
from s3 import s3_str
|
|
#from s3 import S3CRUD, s3_str
|
|
# Normal Action Buttons
|
|
#S3CRUD.action_buttons(r)
|
|
# Custom Action Buttons
|
|
auth = current.auth
|
|
deletable = current.db(auth.s3_accessible_query("delete", "req_need_response_line")).select(table.id)
|
|
restrict_d = [str(row.id) for row in deletable]
|
|
s3.actions = [{"label": s3_str(T("Open")),
|
|
"_class": "action-btn",
|
|
"url": URL(f="need_response", vars={"line": "[id]"}),
|
|
},
|
|
{"label": s3_str(T("Delete")),
|
|
"_class": "delete-btn",
|
|
"url": URL(args=["[id]", "delete"]),
|
|
"restrict": restrict_d,
|
|
},
|
|
]
|
|
|
|
# Custom Create Button
|
|
add_btn = DIV(DIV(DIV(A(T("Add Activity"),
|
|
_class = "action-btn",
|
|
_href = URL(f="need_response", args="create"),
|
|
),
|
|
_id = "list-btn-add",
|
|
),
|
|
_class = "widget-container with-tabs",
|
|
),
|
|
_class = "section-container",
|
|
)
|
|
output["common"][0] = add_btn
|
|
|
|
return output
|
|
s3.postp = postp
|
|
|
|
return attr
|
|
|
|
settings.customise_req_need_response_line_controller = customise_req_need_response_line_controller
|
|
|
|
# =============================================================================
|
|
class NeedResponseLineReportRepresent(S3ReportRepresent):
|
|
"""
|
|
Custom representation of need response line records in
|
|
pivot table reports:
|
|
- show as location name
|
|
"""
|
|
|
|
def __call__(self, record_ids):
|
|
"""
|
|
Represent record_ids (custom)
|
|
|
|
@param record_ids: req_need_response_line record IDs
|
|
|
|
@returns: a JSON-serializable dict {recordID: representation}
|
|
"""
|
|
|
|
# Represent the location IDs
|
|
resource = current.s3db.resource("req_need_response_line",
|
|
id = record_ids,
|
|
)
|
|
|
|
rows = resource.select(["id", "coarse_location_id", "location_id"],
|
|
represent = True,
|
|
raw_data = True,
|
|
limit = None,
|
|
).rows
|
|
|
|
output = {}
|
|
for row in rows:
|
|
raw = row["_row"]
|
|
if raw["req_need_response_line.location_id"]:
|
|
repr_str = row["req_need_response_line.location_id"]
|
|
else:
|
|
# Fall back to coarse_location_id if no GN available
|
|
repr_str = row["req_need_response_line.coarse_location_id"]
|
|
output[raw["req_need_response_line.id"]] = repr_str
|
|
|
|
return output
|
|
|
|
# END =========================================================================
|