Spotter-VM/lxc-apps/share/install/sahana_data/SHARE/config.py

2553 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.unauthorised()
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 =========================================================================