From 41218a42163f61bb4a289ed37502ea385cc0ec13 Mon Sep 17 00:00:00 2001 From: Disassembler Date: Tue, 8 Mar 2022 11:32:59 +0100 Subject: [PATCH] Populate users for Spotter template --- .../eden/modules/templates/SAMBRO/config.py | 1595 ----------------- .../eden/modules/templates/Spotter/config.py | 18 +- .../eden/modules/templates/Spotter/css.cfg | 2 + 3 files changed, 14 insertions(+), 1601 deletions(-) delete mode 100644 sahana-spotter/image.d/srv/web2py/applications/eden/modules/templates/SAMBRO/config.py diff --git a/sahana-spotter/image.d/srv/web2py/applications/eden/modules/templates/SAMBRO/config.py b/sahana-spotter/image.d/srv/web2py/applications/eden/modules/templates/SAMBRO/config.py deleted file mode 100644 index 36ebaa8..0000000 --- a/sahana-spotter/image.d/srv/web2py/applications/eden/modules/templates/SAMBRO/config.py +++ /dev/null @@ -1,1595 +0,0 @@ -# -*- coding: utf-8 -*- - -import json -import os - -from collections import OrderedDict -from io import BytesIO - -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.append("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 - 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" - # Subscriptions - settings.msg.notify_check_subscriptions = True - - # ------------------------------------------------------------------------- - # 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 = {"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 s3_action_buttons - 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 - s3_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]"}, - ) - s3_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 - - Args: - resource: the S3Resource - data: the data returned from S3Resource.select - meta_data: the meta data for the notification - 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 - - Args: - resource: the S3Resource - data: the data returned from S3Resource.select - 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 - - Args: - resource: the S3Resource - data: the data returned from S3Resource.select - 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 - - Args: - resource: the S3Resource - data: the data returned from S3Resource.select - 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 - )), - ("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 - - Args: - row: the row from which the email will be constructed - ack_id: cap_alert_ack.id for including the acknowledgement link - 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 - - Args: - row: the row from which the sms will be constructed - ack_id: cap_alert_ack.id for including the acknowledgement link - 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 - - Args: - alert_id: The particular alert ID for acknowledging - 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, {"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 - - Args: - alert_id: the cap_alert record ID - - Returns: - doc_id of the CAP file - """ - - s3db = current.s3db - rtable = s3db.cap_resource - dtable = s3db.doc_document - - # Check for existing CAP XML resource - 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.doc_id, - limitby = (0, 1), - ).first() - if row: - return row.doc_id - - # Create a CAP resource for the CAP XML file - 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) - - # Post-process the CAP resource - record["id"] = resource_id - s3db.update_super(rtable, record) - doc_id = record["doc_id"] - auth = current.auth - auth.s3_set_record_owner(rtable, resource_id) - auth.s3_make_session_owner(rtable, resource_id) - s3db.onaccept("cap_resource", record, method="create") - - # Generate the CAP XML - resource = s3db.resource("cap_alert", id=alert_id) - cap_xml = resource.export_xml( - stylesheet = os.path.join(current.request.folder, - "static", - "formats", - "cap", - "export.xsl", - ), - pretty_print = True, - ) - - stream = BytesIO(cap_xml) - filename = "%s_%s.xml" % ("cap_alert", s3_str(alert_id)) - - # Store the CAP XML as doc_document for the CAP resource - document = {"file": dtable.file.store(stream, filename), - "doc_id": doc_id, - } - document_id = dtable.insert(**document) - - return document_id - -# END ========================================================================= diff --git a/sahana-spotter/image.d/srv/web2py/applications/eden/modules/templates/Spotter/config.py b/sahana-spotter/image.d/srv/web2py/applications/eden/modules/templates/Spotter/config.py index 1fb76b3..1807160 100644 --- a/sahana-spotter/image.d/srv/web2py/applications/eden/modules/templates/Spotter/config.py +++ b/sahana-spotter/image.d/srv/web2py/applications/eden/modules/templates/Spotter/config.py @@ -23,7 +23,7 @@ def config(settings): # ["default", "default/users"] # Unless doing a manual DB migration, where prepopulate = 0 # In Production, prepopulate = 0 (to save 1x DAL hit every page) - settings.base.prepopulate.append("Spotter") + settings.base.prepopulate.extend(("Spotter", "default/users")) # Uncomment this to prefer scalability-optimized strategies globally #settings.base.bigtable = True @@ -1276,11 +1276,17 @@ def config(settings): restricted = True, module_type = 10, )), - ("vulnerability", Storage( - name_nice = T("Vulnerability"), - #description = "Manages vulnerability indicators", - restricted = True, - module_type = 10, + #("vulnerability", Storage( + # name_nice = T("Vulnerability"), + # #description = "Manages vulnerability indicators", + # restricted = True, + # module_type = 10, + #)), + ("work", Storage( + name_nice = T("Jobs"), + #description = "Simple Volunteer Jobs Management", + restricted = False, + module_type = None, )), # These are specialist modules ("cap", Storage( diff --git a/sahana-spotter/image.d/srv/web2py/applications/eden/modules/templates/Spotter/css.cfg b/sahana-spotter/image.d/srv/web2py/applications/eden/modules/templates/Spotter/css.cfg index d4e4800..a8c1322 100644 --- a/sahana-spotter/image.d/srv/web2py/applications/eden/modules/templates/Spotter/css.cfg +++ b/sahana-spotter/image.d/srv/web2py/applications/eden/modules/templates/Spotter/css.cfg @@ -5,6 +5,7 @@ ../themes/default/shortcut.css ../themes/default/homepage.css font-awesome/font-awesome.css +#plugins/fancyzoom.css plugins/jquery.cluetip.css plugins/jquery.dataTables.css plugins/jquery.dataTables.responsive.css @@ -46,5 +47,6 @@ d3/nv.d3.css ../themes/default/survey.css ../themes/default/newsfeed.css ../themes/default/theme.css + #../themes/default/style.css # Final line required for parsing