# -*- 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 s3compat import BytesIO
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
    # 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"
    # 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" % ("<html>",
                                                    XML(get_html_email_content(arow,
                                                                    ack_id=ack_id,
                                                                    system=False)),
                                                    "</html>")
                        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" % ("<html>",
                                                XML(html_content),
                                                "</html>")
                    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 = "<b>%s:</b> %s" % (T("Event Type"), event_type)
                    else:
                        display_text = "<b>%s:</b> %s" % (T("Event Type"), T("No filter"))
                    if len(priorities_id) > 0:
                        display_text = "%s<br/><b>%s</b>: %s" % (display_text, T("Priorities"), ", ".join(priorities))
                    else:
                        display_text = "%s<br/><b>%s:</b> %s" % (display_text, T("Priorities"), T("No filter"))
                    if len(languages) > 0:
                        display_text = "%s<br/><b>%s:</b> %s" % (display_text, T("Languages"), ", ".join(languages))
                    else:
                        display_text = "%s<br/><b>%s:</b> %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, {"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

            @param alert_id: the cap_alert record ID

            @returns: the 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 =========================================================================
