143
149
#(T("Resources"), "resource"),
152
# Only display active volunteers
153
response.s3.filter = (db.pr_person.id == db.vol_volunteer.person_id) & (db.vol_volunteer.status == 1)
155
db.pr_presence.presence_condition.default = vita.CONFIRMED
156
db.pr_presence.presence_condition.readable = False
157
db.pr_presence.presence_condition.writable = False
158
db.pr_presence.orig_id.readable = False
159
db.pr_presence.orig_id.writable = False
160
db.pr_presence.dest_id.readable = False
161
db.pr_presence.dest_id.writable = False
162
db.pr_presence.proc_desc.readable = False
163
db.pr_presence.proc_desc.writable = False
146
165
output = s3_rest_controller("pr", resourcename,
147
166
rheader=lambda r: shn_pr_rheader(r, tabs))
153
# -----------------------------------------------------------------------------
156
""" RESTful CRUD controller """
159
(T("Basic Details"), None),
160
(T("Staff"), "staff"),
161
(T("Tasks"), "task"),
162
#(T("Donors"), "organisation"),
163
#(T("Sites"), "site"), # Ticket 195
166
rheader = lambda r: shn_project_rheader(r, tabs)
167
return s3_rest_controller("project", resourcename, rheader=rheader)
170
# -----------------------------------------------------------------------------
173
""" Manage current user's tasks """
175
tablename = "project_%s" % (resourcename)
176
table = db[tablename]
178
my_person_id = s3_logged_in_person()
181
session.error = T("No person record found for current user.")
182
redirect(URL(r=request, f="index"))
184
table.person_id.default = my_person_id
185
#table.person_id.writable = False
187
response.s3.filter = (db.project_task.person_id == my_person_id)
189
s3.crud_strings[tablename].title_list = T("My Tasks")
190
s3.crud_strings[tablename].subtitle_list = T("Task List")
192
return s3_rest_controller("project", resourcename)
176
table1 = db.vol_skill
177
table2 = db.vol_skill_types
178
table3 = db.pr_person
179
vol_id = db((table2.id == table1.skill_types_id) & \
180
(table2.name == "medical")).select(table1.person_id, limitby=(0, 1)).first()
181
person_details = db((table3.id == vol_id.person_id)).select(table3.first_name, table3.last_name, limitby=(0,1)).first()
183
html = DIV(LABEL(vita.fullname(person_details)), DIV(T("Skills") + ": "), UL(skillset), _id="table-container")
184
person_data = "<div>%s</div>" % (person_details)
185
return dict(html=person_data)
187
def showSkillOptions():
190
Search for Volunteers by Skill Type
191
- A Notification is sent to each matching volunteer
193
@ToDo: Make into a normal search_simple? (may need minor modification)
194
@ToDo: Make the Notification into a separate button (may want to search without notifications)
197
vol_skill_type_widget = CheckboxesWidget().widget(db.vol_skill.skill_types_id, None)
198
search_btn = INPUT(_value = "search", _type = "submit")
199
search_form = FORM(vol_skill_type_widget, search_btn)
200
output = dict(search_form = search_form)
203
if search_form.accepts(request.vars, session, keepvalues=True):
204
search_skill_ids = request.vars.skill_types_id
206
table1 = db.vol_skill
207
table2 = db.vol_skill_types
208
table3 = db.pr_person
210
# Print a list of volunteers with their skills status.
211
# @ToDo: selects for only one skills right now. add displaying of skill name
212
vol_id = db((table2.id == table1.skill_types_id) & \
213
(table2.id == search_skill_ids)).select(table1.person_id)
216
html = DIV(DIV(B(T("List of Volunteers for this skills set"))))
218
vol_idset.append(id.person_id)
221
for pe_id in vol_idset:
222
person_details = db((table3.id == pe_id)).select(table3.first_name, table3.middle_name, table3.last_name).first()
223
skillset = db(table1.person_id == pe_id).select(table1.status).first()
224
html.append(DIV(LABEL(vita.fullname(person_details)),DIV(T("Skill Status") + ": "), UL(skillset.status)))
225
# @ToDo: Make the notification message configurable
226
msg.send_by_pe_id(pe_id, "CERT: Please Report for Duty", "We ask you to report for duty if you are available", 1, 1)
228
html.append(DIV(B(T("Volunteers were notified!"))))
229
#for one_pr in person_details:
230
#skillset = "approved"
231
#html += DIV(LABEL(vita.fullname(one_pr)),DIV(T("Skill Status") + ": "), UL(skillset), _id="table-container")
232
#person_data="<div>"+str(person_details)+"</div>"
233
html2 = DIV(html, _id="table-container")
234
output["table"] = html2
195
238
# -----------------------------------------------------------------------------
196
239
def skill_types():
333
339
# -----------------------------------------------------------------------------
336
""" Select skills a volunteer has. """
338
return s3_rest_controller(prefix, "skill")
341
# -----------------------------------------------------------------------------
344
""" RESTful CRUD controller """
347
(T("Basic Details"), None),
348
#(T("Staff"), "staff"),
349
(T("Tasks"), "task"),
350
#(T("Donors"), "organisation"),
351
#(T("Sites"), "site"), # Ticket 195
354
rheader = lambda r: shn_project_rheader(r, tabs)
355
return s3_rest_controller("project", resourcename, rheader=rheader)
358
# -----------------------------------------------------------------------------
361
""" Manage current user's tasks """
363
tablename = "project_%s" % (resourcename)
364
table = db[tablename]
366
my_person_id = s3_logged_in_person()
369
session.error = T("No person record found for current user.")
370
redirect(URL(r=request, f="index"))
372
table.person_id.default = my_person_id
373
#@ToDo: if not a team leader then:
374
# can only assign themselves tasks
376
response.s3.filter = (db.project_task.person_id == my_person_id)
378
s3.crud_strings[tablename].title_list = T("My Tasks")
379
s3.crud_strings[tablename].subtitle_list = T("Task List")
381
return s3_rest_controller("project", resourcename)
384
# -----------------------------------------------------------------------------
386
# -----------------------------------------------------------------------------
390
Show Location of a Volunteer on the Map
392
Use most recent presence if available, else any address that's available.
394
@ToDo: Convert to a custom method of the person resource
397
person_id = request.args(0)
400
persons = db.pr_person
401
presences = db.pr_presence
402
locations = db.gis_location
404
# Include the person's last verified location, assuming that they're not missing
405
presence_query = (persons.id == person_id) & \
406
(persons.missing == False) & \
407
(presences.pe_id == persons.pe_id) & \
408
(presences.presence_condition.belongs(vita.PERSISTANT_PRESENCE)) & \
409
(presences.closed == False) & \
410
(locations.id == presences.location_id)
412
# Need sql.Rows object for show_map, so don't extract individual row yet.
413
features = db(presence_query).select(locations.id,
421
address_query = (persons.id == person_id) & \
422
(db.pr_address.pe_id == persons.pe_id) & \
423
(locations.id == db.pr_address.location_id)
424
# @ToDo: Lookup their schedule to see whether they should be at Work, Home or Holiday & lookup the correct address
425
# For now, take whichever address is supplied first.
426
features = db(address_query).select(locations.id,
433
# Center and zoom the map.
434
record = features.first()
435
lat = record.gis_location.lat
436
lon = record.gis_location.lon
439
config = gis.get_config()
441
if not deployment_settings.get_security_map() or shn_has_role("MapAdmin"):
442
catalogue_toolbar = True
444
catalogue_toolbar = False
446
# Standard Feature Layers
448
feature_layers = db(db.gis_layer_feature.enabled == True).select()
449
for layer in feature_layers:
450
_layer = gis.get_feature_layer(layer.module, layer.resource, layer.name, layer.popup_label, config=config, marker_id=layer.marker_id, active=False, polygons=layer.polygons)
452
feature_queries.append(_layer)
454
# Add the Volunteer layer
456
marker_id = db(db.gis_marker.name == "volunteer").select().first().id
460
# Can't use this since the location_id link is via pr_presence not pr_person
461
#_layer = gis.get_feature_layer("pr", "person", "Volunteer", "Volunteer", config=config, marker_id=marker_id, active=True, polygons=False)
463
# feature_queries.append(_layer)
465
# Insert the name into the query & replace the location_id with the person_id
466
for i in range(0, len(features)):
467
features[i].gis_location.name = vita.fullname(db(db.pr_person.id == features[i].pr_person.id).select(limitby=(0, 1)).first())
468
features[i].gis_location.id = features[i].pr_person.id
470
feature_queries.append({"name" : "Volunteer",
473
"popup_label" : "Volunteer",
474
"popup_url" : URL(r=request, c="vol", f="popup") + "/<id>/read.plain",
475
"marker" : marker_id})
478
feature_queries = feature_queries,
479
catalogue_toolbar = catalogue_toolbar,
480
catalogue_overlays = True,
486
window = False # We should provide a button within the map to make it go full-screen (ideally without reloading the page!)
488
response.view = "vol/view_map.html"
489
return dict(map=html)
491
# Redirect to person details if no location is available
492
session.error = T("No location found")
493
redirect(URL(r=request, c="vol", f="person", args=[person_id, "presence"]))
498
Controller that returns a person's data
499
To be used to populate map popup
502
person_id = request.args(0)
504
vol_query = (db.pr_person.id == person_id)
505
vol = db(vol_query).select(db.pr_person.first_name, db.pr_person.middle_name, db.pr_person.last_name, limitby=(0, 1)).first()
507
skill_query = (db.vol_skill.person_id == person_id) & (db.vol_skill.skill_types_id == db.vol_skill_types.id)
508
skills = db(skill_query).select(db.vol_skill_types.name)
513
skillset.append(s.name)
515
if len(skillset) == 0:
516
skillset.append(T("n/a"))
518
html = DIV(LABEL(vita.fullname(vol)), DIV(T("Skills") + ": "), UL(skillset), _id="table-container")
520
return dict(html=html)
341
522
# -----------------------------------------------------------------------------
342
523
def view_team_map():
344
""" Map Location of Volunteer in a Team.
345
Use most recent presence if available, else any address that's available.
526
Show Location of a Team of Volunteers on the Map
528
Use most recent presence if available
530
@ToDo: Fallback to addresses
532
@ToDo: Convert to a custom method of the group resource
349
535
group_id = request.args(0)
351
537
members_query = (db.pr_group_membership.group_id == group_id)
352
members = db(members_query).select(db.pr_group_membership.person_id) #members of a team aka group
353
member_person_ids = [ x.person_id for x in members ] #list of members
355
#Presence Data of the members with Presence Logs
356
presence_rows = db(db.pr_person.id.belongs(member_person_ids) & \
357
(db.pr_presence.pe_id == db.pr_person.pe_id) & \
358
(db.gis_location.id == db.pr_presence.location_id)).select(db.gis_location.ALL, db.pr_person.id, orderby=~db.pr_presence.datetime)
359
#Get Latest Presence Data
360
person_location_sort = presence_rows.sort(lambda row:row.pr_person.id)
361
previous_person_id = None
363
for row in person_location_sort:
364
if row.pr_person.id != previous_person_id:
365
locations_list.append(row["gis_location"])
366
member_person_ids.remove(row.pr_person.id)
367
previous_person_id = row.pr_person.id
369
#Address of those members without Presence data
370
address = db(db.pr_person.id.belongs(member_person_ids) & \
371
(db.pr_address.pe_id == db.pr_person.pe_id) & \
372
(db.gis_location.id == db.pr_address.location_id)).select(db.gis_location.ALL)
374
locations_list.extend(address)
378
bounds = gis.get_bounds(features=locations_list)
380
volunteer = {"feature_group" : "People"}
382
feature_queries = [{"name" : "Volunteer", "query" : locations_list, "active" : True, "marker" : db(db.gis_marker.name == "volunteer").select().first().id}],
383
feature_groups = [volunteer],
384
wms_browser = {"name" : "Risk Maps", "url" : "http://preview.grid.unep.ch:8080/geoserver/ows?service=WMS&request=GetCapabilities"},
385
catalogue_overlays = True,
386
catalogue_toolbar = True,
538
members = db(members_query).select(db.pr_group_membership.person_id) # Members of a team (aka group)
539
member_person_ids = [ x.person_id for x in members ] # List of members
542
persons = db.pr_person
543
presences = db.pr_presence
544
locations = db.gis_location
546
# Presence Data for Members who aren't Missing & have a Verified Presence
547
features = db(persons.id.belongs(member_person_ids) & \
548
(persons.missing == False) & \
549
(presences.pe_id == persons.pe_id) & \
550
(presences.presence_condition.belongs(vita.PERSISTANT_PRESENCE)) & \
551
(presences.closed == False) & \
552
(locations.id == presences.location_id)).select(locations.id,
561
# Address of those members without Presence data
562
#address = db(persons.id.belongs(member_person_ids) & \
563
# (db.pr_address.pe_id == persons.pe_id) & \
564
# (locations.id == db.pr_address.location_id)).select(locations.id,
568
#locations_list.extend(address)
572
if len(features) > 1:
573
# Set the viewport to the appropriate area to see everyone
574
bounds = gis.get_bounds(features=features)
576
# A 1-person bounds zooms in too far for many tilesets
577
lat = features.first().gis_location.lat
578
lon = features.first().gis_location.lon
581
config = gis.get_config()
583
if not deployment_settings.get_security_map() or shn_has_role("MapAdmin"):
584
catalogue_toolbar = True
586
catalogue_toolbar = False
588
# Standard Feature Layers
590
feature_layers = db(db.gis_layer_feature.enabled == True).select()
591
for layer in feature_layers:
592
_layer = gis.get_feature_layer(layer.module, layer.resource, layer.name, layer.popup_label, config=config, marker_id=layer.marker_id, active=False, polygons=layer.polygons)
594
feature_queries.append(_layer)
596
# Add the Volunteer layer
598
marker_id = db(db.gis_marker.name == "volunteer").select().first().id
602
# Can't use this since the location_id link is via pr_presence not pr_person
603
#_layer = gis.get_feature_layer("pr", "person", "Volunteer", "Volunteer", config=config, marker_id=marker_id, active=True, polygons=False)
605
# feature_queries.append(_layer)
607
# Insert the name into the query & replace the location_id with the person_id
608
for i in range(0, len(features)):
609
features[i].gis_location.name = vita.fullname(db(db.pr_person.id == features[i].pr_person.id).select(limitby=(0, 1)).first())
610
features[i].gis_location.id = features[i].pr_person.id
612
feature_queries.append({"name" : "Volunteers",
615
"popup_label" : "Volunteer",
616
"popup_url" : URL(r=request, c="vol", f="popup") + "/<id>/read.plain",
617
"marker" : marker_id})
621
feature_queries = feature_queries,
622
catalogue_toolbar = catalogue_toolbar,
623
catalogue_overlays = True,
627
window = True, # @ToDo: Change to False & create a way to convert an embedded map to a full-screen one without a screen refresh
631
feature_queries = feature_queries,
632
catalogue_toolbar = catalogue_toolbar,
633
catalogue_overlays = True,
639
window = True, # @ToDo: Change to False & create a way to convert an embedded map to a full-screen one without a screen refresh
641
response.view = "vol/view_map.html"
392
642
return dict(map=html)
394
644
# Redirect to team details if no location is available
395
response.error=T("Add Location")
396
redirect(URL(r=request, c="vol", f="group", args=[group_id,"address"]))
645
session.error=T("Add Location")
646
redirect(URL(r=request, c="vol", f="group", args=[group_id, "address"]))
648
# -----------------------------------------------------------------------------
649
def view_project_map():
652
Show Location of all Tasks on the Map
654
@ToDo: Different Colours for Status
656
Red for Urgent/Incomplete
657
Amber for Non-Urgent/Incomplete
659
@ToDo: A single map with both Tasks & Volunteers displayed on it
661
@ToDo: Convert to a custom method of the project resource
664
project_id = request.args(0)
667
tasks = db.project_task
668
locations = db.gis_location
670
features = db((tasks.project_id == project_id) & \
671
(locations.id == tasks.location_id)).select(locations.id,
685
if len(features) > 1:
686
# Set the viewport to the appropriate area to see all the tasks
687
bounds = gis.get_bounds(features=features)
689
# A 1-task bounds zooms in too far for many tilesets
690
lat = features.first().gis_location.lat
691
lon = features.first().gis_location.lon
694
config = gis.get_config()
696
if not deployment_settings.get_security_map() or shn_has_role("MapAdmin"):
697
catalogue_toolbar = True
699
catalogue_toolbar = False
701
# Standard Feature Layers
703
feature_layers = db(db.gis_layer_feature.enabled == True).select()
704
for layer in feature_layers:
705
_layer = gis.get_feature_layer(layer.module, layer.resource, layer.name, layer.popup_label, config=config, marker_id=layer.marker_id, active=False, polygons=layer.polygons)
707
feature_queries.append(_layer)
709
# Add the Tasks layer
710
# Can't use this since we want to use different colours, not markers
711
#_layer = gis.get_feature_layer("project", "task", "Tasks", "Task", config=config, marker_id=marker_id, active=True, polygons=False)
713
# feature_queries.append(_layer)
715
# Insert the name into the query & replace the location_id with the task_id
716
for i in range(0, len(features)):
717
features[i].gis_location.name = features[i].project_task.subject
718
features[i].gis_location.id = features[i].project_task.id
719
features[i].gis_location.shape = "circle"
720
if features[i].project_task.status in [3, 4, 6]:
721
# Green for 'Completed', 'Postponed' or 'Cancelled'
722
features[i].gis_location.color = "green"
723
elif features[i].project_task.status == 1 and features[i].project_task.urgent == True:
724
# Red for 'Urgent' and 'New' (i.e. Unassigned)
725
features[i].gis_location.color = "red"
727
# Amber for 'Feedback' or 'non-urgent'
728
features[i].gis_location.color = " #FFBF00"
731
feature_queries.append({
735
"popup_label" : "Task",
736
"popup_url" : URL(r=request, c="project", f="task") + "/<id>/read.plain"
742
feature_queries = feature_queries,
743
catalogue_toolbar = catalogue_toolbar,
744
catalogue_overlays = True,
748
window = True, # @ToDo Change to False & create a way to convert an embedded map to a full-screen one without a screen refresh
753
feature_queries = feature_queries,
754
catalogue_toolbar = catalogue_toolbar,
755
catalogue_overlays = True,
761
window = True, # @ToDo Change to False & create a way to convert an embedded map to a full-screen one without a screen refresh
763
response.view = "vol/view_map.html"
764
return dict(map=html)
766
# Redirect to tasks if no task location is available
767
session.error=T("No Tasks with Location Data!")
768
redirect(URL(r=request, c="vol", f="project", args=[project_id, "task"]))
770
# -----------------------------------------------------------------------------
771
def view_offices_map():
774
Show Location of all Offices on the Map
775
- optionally filter by those within a radius of a specific Event (Project)
781
if "project_id" in request.vars:
782
project_id = request.vars.project_id
784
if "radius" in request.vars:
785
radius = request.vars.radius
788
projects = db.project_project
789
offices = db.org_office
790
locations = db.gis_location
792
if project_id and radius:
793
# @ToDo: Optimise by doing a single SQL query with the Spatial one
794
project_locations = db((projects.id == project_id) & (locations.id == projects.location_id)).select(locations.id,
804
project_location = project_locations.first()
805
lat = project_location.gis_location.lat
806
lon = project_location.gis_location.lon
808
if (lat is None) or (lon is None):
810
session.error = T("Project has no Lat/Lon")
811
redirect(URL(r=request, c="vol", f="project", args=[project_id]))
813
# Perform the Spatial query
814
features = gis.get_features_in_radius(lat, lon, radius, tablename="org_office")
816
# @ToDo: we also want the Project to show (with different Icon): project_locations set ready
819
features = db((offices.id > 0) & \
820
(locations.id == offices.location_id)).select(locations.id,
832
if len(features) > 1:
833
# Set the viewport to the appropriate area to see all the tasks
834
bounds = gis.get_bounds(features=features)
836
# A 1-task bounds zooms in too far for many tilesets
837
lat = features[0].gis_location.lat
838
lon = features[0].gis_location.lon
841
config = gis.get_config()
843
if not deployment_settings.get_security_map() or shn_has_role("MapAdmin"):
844
catalogue_toolbar = True
846
catalogue_toolbar = False
848
# Standard Feature Layers
850
feature_layers = db(db.gis_layer_feature.enabled == True).select()
851
for layer in feature_layers:
852
_layer = gis.get_feature_layer(layer.module, layer.resource, layer.name, layer.popup_label, config=config, marker_id=layer.marker_id, active=False, polygons=layer.polygons)
854
feature_queries.append(_layer)
856
# Add the Offices layer
857
# Can't use this since we may have a custom spatial query
858
#_layer = gis.get_feature_layer("org", "office", "Offices", "Office", config=config, marker_id=marker_id, active=True, polygons=False)
860
# feature_queries.append(_layer)
863
office_marker_id = db(db.gis_marker.name == "office").select().first().id
867
# Insert the name into the query & replace the location_id with the office_id
868
for i in range(0, len(features)):
869
features[i].gis_location.name = features[i].org_office.name
870
features[i].gis_location.id = features[i].org_office.id
872
# features[i].gis_location.shape = "circle"
873
# if features[i].project_task.status in [3, 4, 6]:
874
# # Green for 'Completed', 'Postponed' or 'Cancelled'
875
# features[i].gis_location.color = "green"
876
# elif features[i].project_task.status == 1 and features[i].project_task.urgent == True:
877
# # Red for 'Urgent' and 'New' (i.e. Unassigned)
878
# features[i].gis_location.color = "red"
880
# # Amber for 'Feedback' or 'non-urgent'
881
# features[i].gis_location.color = " #FFBF00"
883
feature_queries.append({
887
"popup_label" : "Task",
888
"popup_url" : URL(r=request, c="org", f="office") + "/<id>/read.plain",
889
"marker" : office_marker_id
895
feature_queries = feature_queries,
896
catalogue_toolbar = catalogue_toolbar,
897
catalogue_overlays = True,
901
window = True, # @ToDo: Change to False & create a way to convert an embedded map to a full-screen one without a screen refresh
906
feature_queries = feature_queries,
907
catalogue_toolbar = catalogue_toolbar,
908
catalogue_overlays = True,
914
window = True, # @ToDo: Change to False & create a way to convert an embedded map to a full-screen one without a screen refresh
917
response.view = "vol/view_map.html"
918
return dict(map=html)
921
# Redirect to offices if none found
922
session.error=T("No Offices found!")
923
redirect(URL(r=request, c="org", f="office"))
925
# -----------------------------------------------------------------------------
399
927
# -----------------------------------------------------------------------------
400
928
def compose_person():