# -*- coding: utf-8 -*- import json import os from collections import OrderedDict from gluon import current from gluon.html import * from gluon.storage import Storage from gluon.languages import lazyT from s3 import FS, s3_str, s3_truncate, s3_utc def config(settings): """ Template settings for CAP: Common Alerting Protocol """ T = current.T ## Deprecated because such edits to the title should happen in the 000_config.py file ## specific to the demo deployment and not here because if you change here it affect any ## developments and commits to git etc - nuwan at sahanafoundation dot org #settings.base.system_name = T("Sahana Alerting and Messaging Broker") #settings.base.system_name_short = T("SAMBRO") ## # Pre-Populate settings.base.prepopulate += ("SAMBRO",) settings.base.prepopulate_demo += ("SAMBRO/Demo",) # Theme (folder to use for views/layout.html) #settings.base.theme = "SAMBRO" # The Registration functionality shouldn't be visible to the Public #settings.security.registration_visible = True settings.auth.registration_requires_approval = True # Link Users to Organisations settings.auth.registration_requests_organisation = True # GeoNames username settings.gis.geonames_username = "trendspotter" settings.gis.simplify_tolerance = 0 # ========================================================================= # System Settings # ------------------------------------------------------------------------- # Security Policy # http://eden.sahanafoundation.org/wiki/S3/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 = 4 # Controller-Function ACLs # Record Approval settings.auth.record_approval = True # cap_alert record requires approval before sending settings.auth.record_approval_required_for = ("cap_alert",) # Don't auto-approve so that can save draft settings.auth.record_approval_manual = ("cap_alert",) # ========================================================================= # Module Settings # ------------------------------------------------------------------------- # CAP Settings # Uncomment this according to country profile #settings.cap.restrict_fields = True # ------------------------------------------------------------------------- # Notifications # Template for the subject line in update notifications #settings.msg.notify_subject = "%s $s %s" % (T("SAHANA"), T("Alert Notification")) # Notifications format settings.msg.notify_email_format = "html" # Filename for FTP # Characters not allowed are [\ / : * ? " < > | % .] # https://en.wikipedia.org/wiki/Filename # http://docs.attachmate.com/reflection/ftp/15.6/guide/en/index.htm?toc.htm?6503.htm settings.sync.upload_filename = "$s-%s" % ("recent_alert") # Whether to tweet alerts settings.cap.post_to_twitter = True # Whether to post alerts in facebook? settings.cap.post_to_facebook = True # ALlow RSS to use links of entry if link fails settings.cap.rss_use_links = True # SAMBRO supports ack workflow settings.cap.use_ack = True # ------------------------------------------------------------------------- # L10n (Localization) settings languages = OrderedDict([ ("cs", "Czech"), ("en-US", "English"), ]) settings.cap.languages = languages settings.L10n.languages = languages # Translate the cap_area name settings.L10n.translate_cap_area = True # Date Format #settings.L10n.date_format = "%a, %d %B %Y" # Time Format settings.L10n.time_format = "%H:%M:%S" # PDF font settings.L10n.pdf_export_font = ['Helvetica', 'Helvetica-Bold'] # ------------------------------------------------------------------------- # Messaging # Parser settings.msg.parser = "SAMBRO" # ------------------------------------------------------------------------- # Organisations # Enable the use of Organisation Branches settings.org.branches = True # ------------------------------------------------------------------------- def customise_msg_rss_channel_resource(r, tablename): # @ToDo: We won't be able to automate this as we have 2 sorts, so will need the user to select manually # Can we add a component for the parser for S3CSV imports? s3db = current.s3db def onaccept(form): # Normal onaccept s3db.msg_channel_onaccept(form) db = current.db table = db.msg_rss_channel form_vars = form.vars record_id = form_vars.get("id", None) form_type = form_vars.get("type", None) type = current.request.get_vars.get("type", None) query = (table.id == record_id) if type == "cap" or form_type == "cap": fn = "parse_rss_2_cap" db(query).update(type = "cap") else: fn = "parse_rss_2_cms" db(query).update(type = "cms") channel_id = db(query).select(table.channel_id, limitby=(0, 1)).first().channel_id # Link to Parser table = s3db.msg_parser parser_id = table.insert(channel_id=channel_id, function_name=fn, enabled=True) s3db.msg_parser_enable(parser_id) run_async = current.s3task.run_async # Poll run_async("msg_poll", args=["msg_rss_channel", channel_id]) # Parse run_async("msg_parse", args=[channel_id, fn]) s3db.configure(tablename, create_onaccept = onaccept, ) settings.customise_msg_rss_channel_resource = customise_msg_rss_channel_resource # ------------------------------------------------------------------------- def customise_msg_rss_channel_controller(**attr): s3 = current.response.s3 channel_type = current.request.get_vars.get("type", None) if channel_type == "cap": # CAP RSS Channel s3.filter = (FS("type") == "cap") s3.crud_strings["msg_rss_channel"] = Storage( label_create = T("Add CAP Feed"), title_display = T("CAP Feed"), title_list = T("CAP Feeds"), title_update = T("Edit CAP Feed"), label_list_button = T("List CAP Feeds"), label_delete_button = T("Delete CAP Feed"), msg_record_created = T("CAP Feed created"), msg_record_modified = T("CAP Feed modified"), msg_record_deleted = T("CAP Feed deleted"), msg_list_empty = T("No CAP Feed to show")) else: # CMS RSS Channel s3.filter = (FS("type") == "cms") # Custom postp standard_postp = s3.postp def custom_postp(r, output): # Call standard postp if callable(standard_postp): output = standard_postp(r, output) if r.interactive and isinstance(output, dict): # Modify Open Button if channel_type == "cap": # CAP RSS Channel table = r.table query = (table.deleted == False) rows = current.db(query).select(table.id, table.enabled, ) restrict_e = [str(row.id) for row in rows if not row.enabled] restrict_d = [str(row.id) for row in rows if row.enabled] s3.actions = [{"label": s3_str(T("Open")), "_class": "action-btn edit", "url": URL(args = ["[id]", "update"], vars = {"type": "cap"}, ), }, {"label": s3_str(T("Delete")), "_class": "delete-btn", "url": URL(args = ["[id]", "delete"], vars = {"type": "cap"}, ), }, {"label": s3_str(T("Subscribe")), "_class": "action-btn", "url": URL(args = ["[id]", "enable"], vars = {"type": "cap"}, ), "restrict": restrict_e, }, {"label": s3_str(T("Unsubscribe")), "_class": "action-btn", "url": URL(args = ["[id]", "disable"], vars = {"type": "cap"}, ), "restrict": restrict_d }, ] if not current.s3task._is_alive(): # No Scheduler Running s3.actions.append({"label": s3_str(T("Poll")), "_class": "action-btn", "url": URL(args = ["[id]", "poll"], vars = {"type": "cap"}, ), "restrict": restrict_d, }) if "form" in output and current.auth.s3_has_role("ADMIN"): # Modify Add Button add_btn = A(T("Add CAP Feed"), _class = "action-btn", _href = URL(args = ["create"], vars = {"type": "cap"}, ), ) output["showadd_btn"] = add_btn return output s3.postp = custom_postp return attr settings.customise_msg_rss_channel_controller = customise_msg_rss_channel_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): s3 = current.response.s3 crud_strings_branch = Storage( label_create = T("Add Branch"), title_display = T("Branch Details"), title_list = T("Branches"), title_update = T("Edit Branch"), title_upload = T("Import Branches"), label_list_button = T("List Branches"), label_delete_button = T("Delete Branch"), msg_record_created = T("Branch added"), msg_record_modified = T("Branch updated"), msg_record_deleted = T("Branch deleted"), msg_list_empty = T("No Branches currently registered")) if r.component_name == "branch": # Make sure branch uses same form as organisation because we need CAP OID r.component.actuate = "replace" s3.crud_strings[tablename] = crud_strings_branch if r.method == "hierarchy": s3.crud_strings[tablename] = crud_strings_branch from s3 import S3SQLCustomForm, S3SQLInlineComponent, S3SQLInlineLink crud_form = S3SQLCustomForm("name", "acronym", S3SQLInlineLink("organisation_type", field = "organisation_type_id", label = T("Type"), multiple = False, #widget = "hierarchy", ), S3SQLInlineComponent( "tag", label = T("CAP OID"), multiple = False, fields = [("", "value")], filterby = dict(field = "tag", options = "cap_oid", ), ), "website", "comments", ) current.s3db.configure("org_organisation", crud_form = crud_form, ) settings.customise_org_organisation_resource = customise_org_organisation_resource # ------------------------------------------------------------------------- def customise_pr_person_resource(r, tablename): # On-delete option current.s3db.pr_person_id.attr.ondelete = "SET NULL" settings.customise_pr_person_resource = customise_pr_person_resource # ------------------------------------------------------------------------- def customise_pr_contact_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 table = r.table table.priority.writable = False table.priority.readable = False table.comments.writable = False table.comments.readable = False return result s3.prep = custom_prep return attr settings.customise_pr_contact_controller = customise_pr_contact_controller # ------------------------------------------------------------------------- def customise_cap_alert_resource(r, tablename): T = current.T db = current.db s3db = current.s3db def onapprove(record): # Normal onapprove s3db.cap_alert_onapprove(record) run_async = current.s3task.run_async # Sync FTP Repository run_async("cap_ftp_sync") # @ToDo: Check for LEFT join when required # this is ok for now since every Alert should have an Info & an Area alert_id = int(record["id"]) table = s3db.cap_alert itable = s3db.cap_info atable = s3db.cap_area query = (table.id == alert_id) & \ (table.deleted != True) & \ (itable.alert_id == table.id) & \ (itable.deleted != True) & \ (atable.alert_id == table.id) & \ (atable.deleted != True) resource = s3db.resource("cap_alert", filter=query) # Fields to extract fields = resource.list_fields(key="notify_fields") # Extract the data data = resource.select(fields, raw_data=True) # Single row as we are filtering for particular alert_id arow = data["rows"][0] # Create attachment cap_document_id = _get_or_create_attachment(alert_id) if record["scope"] != "Private" and data["numrows"] > 0: # Google Cloud Messaging stable = s3db.pr_subscription ctable = s3db.pr_contact query = (stable.pe_id == ctable.pe_id) & \ (ctable.contact_method == "GCM") & \ (ctable.value != None) & \ (ctable.deleted != True) & \ (stable.deleted != True) & \ (stable.method.like("%GCM%")) rows = db(query).select(ctable.value) if len(rows): registration_ids = [s3_str(row.value) for row in rows] title = get_email_subject(arow, system=False) run_async("msg_gcm", args=[title, "%s/%s" % (s3_str(arow["cap_info.web"]), "profile"), s3_str(get_formatted_value(arow["cap_info.headline"], system=False)), json.dumps(registration_ids), ]) # Twitter Post if settings.get_cap_post_to_twitter(): try: import tweepy except ImportError: current.log.debug("tweepy module needed for sending tweets") else: url = "%s/%s" % (arow["cap_info.web"], "profile") try: from pyshorteners import Shortener except ImportError: pass else: try: url = Shortener('Tinyurl', timeout=3).short(url) except: pass twitter_text = \ ("""%(status)s Alert: %(headline)s %(sender)s: %(sender_name)s %(website)s: %(Website)s""") % {"status": s3_str(T(arow["cap_alert.status"])), "headline": s3_str(get_formatted_value(arow["cap_info.headline"], system=False)), "sender": s3_str(T("Sender")), "sender_name": s3_str(get_formatted_value(arow["cap_info.sender_name"], system=False)), "website": s3_str(T("Website")), "Website": s3_str(url), } try: # @ToDo: Handle the multi-message nicely? # @ToDo: Send resource url with tweet current.msg.send_tweet(text=s3_str(twitter_text), alert_id=alert_id, ) except tweepy.error.TweepError as e: current.log.debug("Sending tweets failed: %s" % e) # Facebook Post if settings.get_cap_post_to_facebook(): # @ToDo: post resources too? content = get_facebook_content(arow) try: current.msg.post_to_facebook(text=content, alert_id=alert_id, ) except Exception as e: current.log.debug("Posting Alert to Facebook failed: %s" % e) addresses = record["addresses"] if len(addresses): # First Responders gtable = s3db.pr_group mtable = s3db.pr_group_membership ptable = s3db.pr_person send_by_pe_id = current.msg.send_by_pe_id get_user_id = current.auth.s3_get_user_id query_ = (gtable.id == mtable.group_id) & \ (mtable.person_id == ptable.id) & \ (gtable.deleted != True) & \ (mtable.deleted != True) & \ (ptable.deleted != True) count = len(addresses) if count == 1: query = query_ & (gtable.id == addresses[0]) else: query = query_ & (gtable.id.belongs(addresses)) rows = db(query).select(ptable.pe_id) subject = get_email_subject(arow, system=False) if settings.get_cap_use_ack(): for row in rows: ack_id = create_ack(alert_id, get_user_id(pe_id=row.pe_id)) email_content = "%s%s%s" % ("", XML(get_html_email_content(arow, ack_id=ack_id, system=False)), "") sms_content = get_sms_content(arow, ack_id=ack_id, system=False) send_by_pe_id(row.pe_id, subject, email_content, document_ids=cap_document_id, alert_id=alert_id, ) try: send_by_pe_id(row.pe_id, subject, sms_content, contact_method="SMS", alert_id=alert_id, ) except ValueError: current.log.error("No SMS Handler defined!") else: html_content = get_html_email_content(arow, system=False) email_content = "%s%s%s" % ("", XML(html_content), "") sms_content = get_sms_content(arow, system=False) for row in rows: send_by_pe_id(row.pe_id, subject, email_content, document_ids=cap_document_id, alert_id=alert_id, ) try: send_by_pe_id(row.pe_id, subject, sms_content, contact_method="SMS", alert_id=alert_id, ) except ValueError: current.log.error("No SMS Handler defined!") s3db.configure(tablename, onapprove = onapprove, ) settings.customise_cap_alert_resource = customise_cap_alert_resource # ------------------------------------------------------------------------- def customise_cap_alert_controller(**attr): s3 = current.response.s3 auth = current.auth if not auth.user: # For notifications for group r = current.request if not r.function == "public": if r.get_vars.format == "msg": # This is called by notification # The request from web looks like r.extension s3.filter = (FS("scope") != "Private") else: auth.permission.fail() # 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 == "msg": # Notification table = r.table table.scope.represent = None table.status.represent = None table.msg_type.represent = None itable = current.s3db.cap_info itable.severity.represent = None itable.urgency.represent = None itable.certainty.represent = None return result s3.prep = custom_prep return attr settings.customise_cap_alert_controller = customise_cap_alert_controller # ------------------------------------------------------------------------- def customise_sync_repository_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 == "popup": table = r.table table.apitype.default = "ftp" table.apitype.readable = table.apitype.writable = False table.synchronise_uuids.readable = \ table.synchronise_uuids.writable = False table.uuid.readable = table.uuid.writable = False return result s3.prep = custom_prep return attr settings.customise_sync_repository_controller = customise_sync_repository_controller # ------------------------------------------------------------------------- def customise_pr_subscription_controller(**attr): from s3 import S3CRUD s3 = current.response.s3 s3db = current.s3db auth = current.auth stable = s3db.pr_subscription has_role = auth.s3_has_role list_fields = [(T("Filters"), "filter_id"), (T("Methods"), "method"), ] manage_recipient = current.request.get_vars["option"] == "manage_recipient" role_check = has_role("ADMIN") if manage_recipient and role_check: # Admin based subscription s3.filter = (stable.deleted != True) & \ (stable.owned_by_group != None) list_fields.insert(0, (T("People/Groups"), "pe_id")) s3.crud_strings["pr_subscription"].title_list = T("Admin Controlled Subscriptions") else: # Self Subscription s3.filter = (stable.deleted != True) & \ (stable.owned_by_group == None) & \ (stable.owned_by_user == auth.user.id) s3.crud_strings["pr_subscription"].title_list = T("Your Subscriptions") # Custom prep standard_prep = s3.prep def custom_prep(r): from s3 import S3Represent table = r.table # Call standard prep if callable(standard_prep): result = standard_prep(r) else: result = True MSG_CONTACT_OPTS = {"EMAIL": T("EMAIL"), "SMS" : T("SMS"), "FTP" : T("FTP"), } table.method.represent = S3Represent(options=MSG_CONTACT_OPTS, multiple=True, ), if r.representation == "html": table.filter_id.represent = S3Represent(\ options=pr_subscription_filter_row_options()) s3db.configure("pr_subscription", list_fields = list_fields, list_orderby = "pe_id desc", orderby = "pr_subscription.pe_id desc", ) return result s3.prep = custom_prep # Custom postp standard_postp = s3.postp def custom_postp(r, output): # Call standard postp if callable(standard_postp): output = standard_postp(r, output) if r.interactive and isinstance(output, dict): # Modify Open Button if manage_recipient and role_check: # Admin based subscription S3CRUD.action_buttons(r, update_url=URL(c="default", f="index", args=["subscriptions"], vars={"option": "manage_recipient", "subscription_id": "[id]"} ), delete_url=URL(c="pr", f="subscription", args=["[id]", "delete"], vars={"option": "manage_recipient"} ) ) else: # self subscription url = URL(c="default", f="index", args=["subscriptions"], vars={"subscription_id": "[id]"}) S3CRUD.action_buttons(r, update_url=url, read_url=url) if "form" in output: # Modify Add Button if manage_recipient and role_check: # Admin based subscription add_btn = A(T("Add Recipient to List"), _class="action-btn", _href=URL(c="default", f="index", args=["subscriptions"], vars={"option": "manage_recipient"} ) ) else: # self subscription add_btn = A(T("Create Subscription"), _class="action-btn", _href=URL(c="default", f="index", args=["subscriptions"]) ) output["showadd_btn"] = add_btn return output s3.postp = custom_postp return attr settings.customise_pr_subscription_controller = customise_pr_subscription_controller # ----------------------------------------------------------------------------- def custom_msg_render(resource, data, meta_data, format=None): """ Custom Method to pre-render the contents for the message template @param resource: the S3Resource @param data: the data returned from S3Resource.select @param meta_data: the meta data for the notification @param format: the contents format ("text" or "html") """ notify_on = meta_data["notify_on"] last_check_time = meta_data["last_check_time"] rows = data["rows"] output = {} upd = [] # upd as the created alerts might be approved after some time, check is also done db = current.db atable = current.s3db.cap_alert if format == "text": # For SMS append_record = upd.append for row in rows: row_ = db(atable.id == row["cap_alert.id"]).select(atable.approved_on, limitby=(0, 1)).first() if row_ and row_.approved_on is not None: if s3_utc(row_.approved_on) >= last_check_time: sms_content = get_sms_content(row) append_record(sms_content) if "upd" in notify_on and len(upd): output["upd"] = len(upd) output["upd_records"] = upd else: output["upd"] = None else: # HTML emails elements = [] append = elements.append append_record = upd.append for row in rows: row_ = db(atable.id == row["cap_alert.id"]).select(atable.approved_on, limitby=(0, 1)).first() if row_ and row_.approved_on is not None: if s3_utc(row_.approved_on) >= last_check_time: content = get_html_email_content(row) container = DIV(DIV(content)) append(container) append(BR()) append_record(container) if "upd" in notify_on and len(upd): output["upd"] = len(upd) output["upd_records"] = DIV(*elements) else: output["upd"] = None output.update(meta_data) return output settings.msg.notify_renderer = custom_msg_render # ----------------------------------------------------------------------------- def custom_msg_notify_subject(resource, data, meta_data): """ Custom Method to subject for the email @param resource: the S3Resource @param data: the data returned from S3Resource.select @param meta_data: the meta data for the notification """ rows = data["rows"] subject = "%s %s" % (settings.get_system_name_short(), T("Alert Notification")) if len(rows) == 1: # Since if there are more than one row, the single email has content # for all rows atable = current.s3db.cap_alert row_ = current.db(atable.id == rows[0]["cap_alert.id"]).select(atable.approved_on, limitby=(0, 1) ).first() if row_ and row_.approved_on is not None: if s3_utc(row_.approved_on) >= meta_data["last_check_time"]: subject = get_email_subject(rows[0]) return subject settings.msg.notify_subject = custom_msg_notify_subject # ----------------------------------------------------------------------------- def custom_msg_notify_attachment(resource, data, meta_data): """ Custom Method to get the document_ids to be sent as attachment @param resource: the S3Resource @param data: the data returned from S3Resource.select @param meta_data: the meta data for the notification """ rows = data["rows"] document_ids = [] dappend = document_ids.append for row in rows: alert_id = row["cap_alert.id"] document_id = _get_or_create_attachment(alert_id) dappend(document_id) return document_ids settings.msg.notify_attachment = custom_msg_notify_attachment # ----------------------------------------------------------------------------- def custom_msg_notify_send_data(resource, data, meta_data): """ Custom Method to send data containing alert_id to the s3msg.send_by_pe_id @param resource: the S3Resource @param data: the data returned from S3Resource.select @param meta_data: the meta data for the notification """ rows = data.rows data = {} if len(rows) == 1: row = rows[0] if "cap_alert.id" in row: try: alert_id = int(row["cap_alert.id"]) data["alert_id"] = alert_id except ValueError: pass return data settings.msg.notify_send_data = custom_msg_notify_send_data # ----------------------------------------------------------------------------- def msg_send_postprocess(message_id, **data): """ Custom function that links alert_id in cap module to message_id in message module """ alert_id = data.get("alert_id", None) if alert_id and message_id: current.s3db.cap_alert_message.insert(alert_id = alert_id, message_id = message_id) settings.msg.send_postprocess = msg_send_postprocess # ------------------------------------------------------------------------- # Comment/uncomment modules here to disable/enable them # @ToDo: Have the system automatically enable migrate if a module is enabled # Modules menu is defined in modules/eden/menu.py settings.modules = OrderedDict([ # Core modules which shouldn't be disabled ("default", Storage( name_nice = T("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 = T("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 = T("Administration"), #description = "Site Administration", restricted = True, module_type = None # No Menu )), ("errors", Storage( name_nice = T("Ticket Viewer"), #description = "Needed for Breadcrumbs", restricted = False, module_type = None # No Menu )), ("sync", Storage( name_nice = T("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 = T("Mapping"), #description = "Situation Awareness & Geospatial Analysis", restricted = True, module_type = 6, # 6th item in the menu )), ("pr", Storage( name_nice = T("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 = T("Organizations"), #description = 'Lists "who is doing what & where". Allows relief agencies to coordinate their activities', restricted = True, module_type = 10 )), # All modules below here should be possible to disable safely #("hrm", Storage( # name_nice = T("Staff"), # #description = "Human Resources Management", # restricted = True, # module_type = 2, #)), ("cap", Storage( name_nice = T("Alerting"), #description = "Create & broadcast CAP alerts", restricted = True, module_type = 1, )), ("cms", Storage( name_nice = T("Content Management"), #description = "Content Management System", restricted = True, module_type = 10, )), ("doc", Storage( name_nice = T("Documents"), #description = "A library of digital resources, such as photos, documents and reports", restricted = True, module_type = 10, )), ("msg", Storage( name_nice = T("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, )), ("event", Storage( name_nice = T("Events"), #description = "Activate Events (e.g. from Scenario templates) for allocation of appropriate Resources (Human, Assets & Facilities).", restricted = True, module_type = 10, )), ]) # ------------------------------------------------------------------------- # Functions which are local to this Template # ------------------------------------------------------------------------- def pr_subscription_filter_row_options(): """ Build the options for the pr_subscription filter datatable from query @ToDo complete this for locations """ db = current.db s3db = current.s3db auth = current.auth has_role = auth.s3_has_role stable = s3db.pr_subscription ftable = s3db.pr_filter if current.request.get_vars["option"] == "manage_recipient" and \ (has_role("ALERT_EDITOR") or has_role("ALERT_APPROVER")): # Admin based subscription query = (stable.deleted != True) & \ (stable.owned_by_group != None) else: # Self Subscription query = (stable.deleted != True) & \ (stable.owned_by_group == None) & \ (stable.owned_by_user == auth.user.id) left = ftable.on(ftable.id == stable.filter_id) rows = db(query).select(stable.filter_id, ftable.query, left=left) filter_options = {} if len(rows) > 0: T = current.T etable = s3db.event_event_type ptable = s3db.cap_warning_priority from s3 import IS_ISO639_2_LANGUAGE_CODE languages_dict = dict(IS_ISO639_2_LANGUAGE_CODE.language_codes()) for row in rows: event_type = None priorities_id = [] languages = [] filters = json.loads(row.pr_filter.query) filters = [filter for filter in filters if filter[1] is not None] if len(filters) > 0: for filter in filters: # Get the prefix prefix = s3_str(filter[0]).strip("[]") # Get the value for prefix values = filter[1].split(",") if prefix == "event_type_id__belongs": event_type_id = s3_str(values[0]) row_ = db(etable.id == event_type_id).select(\ etable.name, limitby=(0, 1)).first() event_type = row_.name elif prefix == "info.priority__belongs": priorities_id = [int(s3_str(value)) for value in values] rows_ = db(ptable.id.belongs(priorities_id)).select(ptable.name) priorities = [row_.name for row_ in rows_] elif prefix == "info.language__belongs": languages = [s3_str(languages_dict[value]) for value in values] if event_type is not None: display_text = "%s: %s" % (T("Event Type"), event_type) else: display_text = "%s: %s" % (T("Event Type"), T("No filter")) if len(priorities_id) > 0: display_text = "%s
%s: %s" % (display_text, T("Priorities"), ", ".join(priorities)) else: display_text = "%s
%s: %s" % (display_text, T("Priorities"), T("No filter")) if len(languages) > 0: display_text = "%s
%s: %s" % (display_text, T("Languages"), ", ".join(languages)) else: display_text = "%s
%s: %s" % (display_text, T("Languages"), T("No filter")) filter_options[row["pr_subscription.filter_id"]] = display_text else: filter_options[row["pr_subscription.filter_id"]] = T("No filters") return filter_options # ------------------------------------------------------------------------- def get_html_email_content(row, ack_id=None, system=True): """ prepare the content for html email @param row: the row from which the email will be constructed @param ack_id: cap_alert_ack.id for including the acknowledgement link @param system: is this system notification email or email for first responders """ itable = current.s3db.cap_info event_type_id = row["cap_info.event_type_id"] priority_id = row["cap_info.priority"] response_type = row["_row"]["cap_info.response_type"] if system else row["cap_info.response_type"] instruction = row["_row"]["cap_info.instruction"] if system else row["cap_info.instruction"] description = row["_row"]["cap_info.description"] if system else row["cap_info.description"] status = row["cap_alert.status"] msg_type = row["cap_alert.msg_type"] url = "%s/%s" % (row["cap_info.web"], "profile") try: from pyshorteners import Shortener except ImportError: pass else: try: url = Shortener('Tinyurl', timeout=3).short(url) except: pass if event_type_id and event_type_id != current.messages["NONE"]: if not isinstance(event_type_id, lazyT) and \ not isinstance(event_type_id, DIV): event_type = itable.event_type_id.represent(event_type_id) else: event_type = event_type_id else: event_type = T("None") if priority_id and priority_id != current.messages["NONE"]: if not isinstance(priority_id, lazyT) and \ not isinstance(priority_id, DIV): priority = itable.priority.represent(priority_id) else: priority = priority_id else: priority = T("Alert") email_content = TAG[""](HR(), BR(), B(s3_str("%s %s %s" % (T(status.upper()), T(status.upper()), T(status.upper())))) if status != "Actual" else "", BR() if status != "Actual" else "", BR() if status != "Actual" else "", A(T("VIEW ALERT ON THE WEB"), _href = s3_str(url)), BR(), BR(), B(s3_str("%s %s %s %s" % (T(row["cap_alert.scope"]), T(status), T("Alert") if msg_type != "Alert" else "", s3_str(msg_type) ))), H2(T(s3_str(get_formatted_value(row["cap_info.headline"], system=system)))), BR(), XML("%(label)s: %(identifier)s" % {"label": B(T("ID")), "identifier": s3_str(row["cap_alert.identifier"]) }), BR(), BR(), T("""%(priority)s message %(message_type)s in effect for %(area_description)s""") % \ {"priority": s3_str(priority), "message_type": s3_str(msg_type), "area_description": s3_str(get_formatted_value(row["cap_area.name"], system=system)), }, BR(), BR(), T("This %(severity)s %(event_type)s is %(urgency)s and is %(certainty)s") %\ {"severity": s3_str(row["cap_info.severity"]), "event_type": s3_str(event_type), "urgency": s3_str(row["cap_info.urgency"]), "certainty": s3_str(row["cap_info.certainty"]), }, BR(), BR(), T("""Message %(identifier)s: %(event_type)s (%(category)s) issued by %(sender_name)s sent at %(date)s from %(source)s""") % \ {"identifier": s3_str(row["cap_alert.identifier"]), "event_type": s3_str(event_type), "category": s3_str(get_formatted_value(row["cap_info.category"], represent = itable.category.represent, system=system)), "sender_name": s3_str(get_formatted_value(row["cap_info.sender_name"], system=system)), "date": s3_str(get_formatted_value(row["cap_alert.sent"], represent = current.s3db.cap_alert.sent.represent, system=system)), "source": s3_str(row["cap_alert.source"]), }, BR(), BR() if description else "", XML("%(label)s: %(alert_description)s" % {"label": B(T("Alert Description")), "alert_description": s3_str(get_formatted_value(description, system=False, ul=True)), }) if description else "", BR() if not isinstance(description, list) else "", BR() if response_type else "", XML(T("%(label)s: %(response_type)s") % {"label": B(T("Expected Response")), "response_type": s3_str(get_formatted_value(response_type, represent = itable.response_type.represent, system=False, ul=True)), }) if response_type else "", BR() if not isinstance(response_type, list) else "", BR() if instruction else "", XML(T("%(label)s: %(instruction)s") % {"label": B(T("Instructions")), "instruction": s3_str(get_formatted_value(instruction, system=False, ul=True)), }) if instruction else "", BR() if not isinstance(instruction, list) else "", BR(), T("Alert is effective from %(effective)s and expires on %(expires)s") % \ {"effective": s3_str(get_formatted_value(row["cap_info.effective"], represent = itable.effective.represent, system=system)), "expires": s3_str(get_formatted_value(row["cap_info.expires"], represent = itable.expires.represent, system=system)), }, BR(), BR(), T("For more details visit %(url)s or contact %(contact)s") % \ {"url": s3_str(url), "contact": s3_str(get_formatted_value(row["cap_info.contact"], system=system)), }, BR(), BR(), T("To acknowledge the alert, use the following link: %(ack_link)s") % \ {"ack_link": "%s%s" % (current.deployment_settings.get_base_public_url(), URL(c="cap", f="alert_ack", args=[ack_id, "update"])), } if ack_id else "", BR() if ack_id else "", BR() if ack_id else "", B(s3_str("%s %s %s" % (T(status.upper()), T(status.upper()), T(status.upper())))) if status != "Actual" else "", ) return email_content # ------------------------------------------------------------------------- def get_email_subject(row, system=True): """ Prepare the subject for Email """ itable = current.s3db.cap_info event_type_id = row["cap_info.event_type_id"] msg_type = T(row["cap_alert.msg_type"]) if event_type_id and event_type_id != current.messages["NONE"]: if not isinstance(event_type_id, lazyT) and \ not isinstance(event_type_id, DIV): event_type = itable.event_type_id.represent(event_type_id) else: event_type = event_type_id else: event_type = T("None") subject = "[%s] %s %s" % (get_formatted_value(row["cap_info.sender_name"], system=system), event_type, msg_type) # RFC 2822 return s3_str(s3_truncate(subject, length=78)) # ------------------------------------------------------------------------- def get_sms_content(row, ack_id=None, system=True): """ Prepare the content for SMS @param row: the row from which the sms will be constructed @param ack_id: cap_alert_ack.id for including the acknowledgement link @param system: is this system notification email or email for first responders """ itable = current.s3db.cap_info event_type_id = row["cap_info.event_type_id"] priority_id = row["cap_info.priority"] url = "%s/%s" % (row["cap_info.web"], "profile") try: from pyshorteners import Shortener except ImportError: pass else: try: url = Shortener('Tinyurl', timeout=3).short(url) except: pass if not isinstance(event_type_id, lazyT) and \ not isinstance(event_type_id, DIV): event_type = itable.event_type_id.represent(event_type_id) else: event_type = event_type_id if priority_id and priority_id != current.messages["NONE"]: if not isinstance(priority_id, lazyT) and \ not isinstance(priority_id, DIV): priority = itable.priority.represent(priority_id) else: priority = priority_id else: priority = T("Unknown") if ack_id: sms_body = \ T("""%(status)s %(message_type)s for %(area_description)s with %(priority)s priority %(event_type)s issued by %(sender_name)s at %(date)s (ID:%(identifier)s) \nTo acknowledge the alert, click: %(ack_link)s \n\n""") % \ {"status": s3_str(row["cap_alert.status"]), "message_type": s3_str(row["cap_alert.msg_type"]), "area_description": s3_str(get_formatted_value(row["cap_area.name"], system=system)), "priority": s3_str(priority), "event_type": s3_str(event_type), "sender_name": s3_str(get_formatted_value(row["cap_info.sender_name"], system=system)), "date": s3_str(row["cap_alert.sent"]), "identifier": s3_str(row["cap_alert.identifier"]), "ack_link": "%s%s" % (current.deployment_settings.get_base_public_url(), URL(c="cap", f="alert_ack", args=[ack_id, "update"])), } else: sms_body = \ T("""%(status)s %(message_type)s for %(area_description)s with %(priority)s priority %(event_type)s issued by %(sender_name)s at %(date)s (ID:%(identifier)s). \nView Alert in web at %(profile)s \n\n""") % \ {"status": s3_str(row["cap_alert.status"]), "message_type": s3_str(row["cap_alert.msg_type"]), "area_description": s3_str(get_formatted_value(row["cap_area.name"], system=system)), "priority": s3_str(priority), "event_type": s3_str(event_type), "sender_name": s3_str(get_formatted_value(row["cap_info.sender_name"], system=system)), "date": s3_str(row["cap_alert.sent"]), "identifier": s3_str(row["cap_alert.identifier"]), "profile": s3_str(url), } return s3_str(sms_body) # ------------------------------------------------------------------------- def get_facebook_content(row, system=False): """ prepare the content for facebook post """ itable = current.s3db.cap_info event_type_id = row["cap_info.event_type_id"] priority_id = row["cap_info.priority"] response_type = row["cap_info.response_type"] instruction = row["cap_info.instruction"] description = row["cap_info.description"] url = "%s/%s" % (row["cap_info.web"], "profile") try: from pyshorteners import Shortener try: url = Shortener('Tinyurl', timeout=3).short(url) except: pass except ImportError: pass if event_type_id and event_type_id != current.messages["NONE"]: if not isinstance(event_type_id, lazyT): event_type = itable.event_type_id.represent(event_type_id) else: event_type = event_type_id else: event_type = T("None") if priority_id and priority_id != current.messages["NONE"]: if not isinstance(priority_id, lazyT): priority = itable.priority.represent(priority_id) else: priority = priority_id else: priority = T("Alert") facebook_content = [ T("%(scope)s %(status)s Alert") % \ {"scope": s3_str(row["cap_alert.scope"]), "status": s3_str(row["cap_alert.status"]), }, T((s3_str(get_formatted_value(row["cap_info.headline"], system=system)))), T("ID: %(identifier)s") % {"identifier": s3_str(row["cap_alert.identifier"])}, T("""%(priority)s message %(message_type)s in effect for %(area_description)s""") % \ {"priority": s3_str(priority), "message_type": s3_str(row["cap_alert.msg_type"]), "area_description": s3_str(get_formatted_value(row["cap_area.name"], system=system)), }, T("This %(severity)s %(event_type)s is %(urgency)s and is %(certainty)s") % \ {"severity": s3_str(row["cap_info.severity"]), "event_type": s3_str(event_type), "urgency": s3_str(row["cap_info.urgency"]), "certainty": s3_str(row["cap_info.certainty"]), }, T("""Message %(identifier)s: %(event_type)s (%(category)s) issued by %(sender_name)s sent at %(date)s from %(source)s""") % \ {"identifier": s3_str(row["cap_alert.identifier"]), "event_type": s3_str(event_type), "category": s3_str(get_formatted_value(row["cap_info.category"], represent = itable.category.represent, system=system)), "sender_name": s3_str(get_formatted_value(row["cap_info.sender_name"], system=system)), "date": s3_str(get_formatted_value(row["cap_alert.sent"], represent = current.s3db.cap_alert.sent.represent, system=system)), "source": s3_str(row["cap_alert.source"]), }, T("Alert Description: %(alert_description)s") % \ {"alert_description": s3_str(get_formatted_value(description, system=system)), } if description else "", T("Expected Response: %(response_type)s") % \ {"response_type": s3_str(get_formatted_value(response_type, represent = itable.response_type.represent, system=system)), } if response_type else "", T("Instruction: %(instruction)s") % \ {"instruction": s3_str(get_formatted_value(instruction, system=system))} if instruction else "", T("Alert is effective from %(effective)s and expires on %(expires)s") % \ {"effective": s3_str(get_formatted_value(row["cap_info.effective"], represent = itable.effective.represent, system=system)), "expires": s3_str(get_formatted_value(row["cap_info.expires"], represent = itable.expires.represent, system=system)), }, T("For more details visit %(url)s or contact %(contact)s") % \ {"url": s3_str(url), "contact": s3_str(get_formatted_value(row["cap_info.contact"], system=system)), } if row["cap_info.contact"] else T("For more details visit %(url)s") % \ {"url": s3_str(url)} ] return "\n\n".join(s3_str(item) for item in facebook_content if item!="") # ------------------------------------------------------------------------- def create_ack(alert_id, user_id): """ Create a specific acknowledgement @param alert_id: The particular alert ID for acknowledging @param user_id: The user ID who owns the record @todo: use location where the alert is targeted for """ ack_data = {"alert_id": alert_id, "owned_by_user": int(user_id), } ack_table = current.s3db.cap_alert_ack ack_id = ack_table.insert(**ack_data) current.auth.s3_set_record_owner(ack_table, ack_id) # Uncomment this when there is onaccept hook #s3db.onaccept(ack_table, dict(id=ack_id)) return ack_id # ------------------------------------------------------------------------- def get_formatted_value(value, represent=None, system=True, ul=False): """ For non-system notification returns the formatted represented value """ if not value: return None else: if system: # For system notification value is already properly formatted for representation return value else: if isinstance(value, list): nvalue = [] for value_ in value: if value_: if represent: nvalue.append(represent(value_)) else: nvalue.append(value_) if len(nvalue): if ul: nvalue = UL(nvalue) else: nvalue = ", ".join(nvalue) else: return None else: if represent: nvalue = represent(value) else: nvalue = value return nvalue # ------------------------------------------------------------------------- def _get_or_create_attachment(alert_id): """ Retrieve the CAP attachment for the alert_id if present else creates CAP file as attachment to be sent with the email returns the document_id for the CAP file """ s3db = current.s3db rtable = s3db.cap_resource dtable = s3db.doc_document query = (rtable.alert_id == alert_id) & \ (rtable.mime_type == "cap") & \ (rtable.deleted != True) & \ (dtable.doc_id == rtable.doc_id) & \ (dtable.deleted != True) row = current.db(query).select(dtable.id, limitby=(0, 1)).first() if row and row.id: return row.id request = current.request auth = current.auth path_join = os.path.join # Create the cap_resource table record = {"alert_id": alert_id, "resource_desc": T("CAP XML File"), "mime_type": "cap" # Hard coded to separate from attachment from user } resource_id = rtable.insert(**record) record["id"] = resource_id s3db.update_super(rtable, record) doc_id = record["doc_id"] auth.s3_set_record_owner(rtable, resource_id) auth.s3_make_session_owner(rtable, resource_id) s3db.onaccept("cap_resource", record, method="create") resource = s3db.resource("cap_alert") resource.add_filter(FS("id") == alert_id) cap_xml = resource.export_xml(stylesheet=path_join(request.folder, "static", "formats", "cap", "export.xsl"), pretty_print=True) file_path = path_join(request.folder, "uploads", "%s_%s.xml" % ("cap_alert", str(alert_id))) file = open(file_path, "w+") file.write(cap_xml) file.close() # Create doc_document record dtable = s3db.doc_document file = open(file_path, "a+") document_id = dtable.insert(**{"file": file, "doc_id": doc_id}) file.close() os.remove(file_path) return document_id # END =========================================================================