text:
try:
    # Python 2.7
    from collections import OrderedDict
except:
    # Python 2.6
    from gluon.contrib.simplejson.ordered_dict import OrderedDict

from datetime import timedelta

from gluon import current
from gluon.html import *
from gluon.storage import Storage

from s3 import s3_avatar_represent

def config(settings):
    """
        Template for Puget Sound Common Maritime Operating Picture (MCOP)

        http://psmcop.org
    """

    T = current.T
    s3 = current.response.s3

    # -----------------------------------------------------------------------------
    # Pre-Populate
    settings.base.prepopulate = ("MCOP", "default/users")

    settings.base.system_name = T("Sahana: Puget Sound Common Maritime Operating Picture (MCOP)")
    settings.base.system_name_short = T("Sahana")

    # =============================================================================
    # System Settings
    # -----------------------------------------------------------------------------
    # Authorization Settings
    # Users can self-register
    #settings.security.self_registration = False
    # Users need to verify their email
    settings.auth.registration_requires_verification = True
    # Users need to be approved
    settings.auth.registration_requires_approval = True
    settings.auth.registration_requests_organisation = True
    settings.auth.registration_organisation_required = True

    # Approval emails get sent to all admins
    settings.mail.approver = "ADMIN"

    settings.auth.registration_link_user_to = {"staff": T("Staff")}
    settings.auth.registration_link_user_to_default = ["staff"]
    settings.auth.registration_roles = {"organisation_id": ["USER"],
                                        }

    settings.auth.show_utc_offset = False
    settings.auth.show_link = False

    # -----------------------------------------------------------------------------
    # Security Policy
    settings.security.policy = 7 # Apply Controller, Function and Table ACLs
    settings.security.map = True

    # -----------------------------------------------------------------------------
    # Theme (folder to use for views/layout.html)
    settings.base.theme = "MCOP"
    settings.ui.formstyle_row = "bootstrap"
    settings.ui.formstyle = "bootstrap"
    settings.ui.filter_formstyle = "bootstrap"
    settings.ui.hide_report_options = False
    # Uncomment to use S3MultiSelectWidget on all dropdowns (currently the Auth Registration page & LocationSelectorWidget2 listen to this)
    settings.ui.multiselect_widget = "search"

    # @ToDo: Investigate
    settings.ui.use_button_icons = True

    # Custom icon classes
    settings.ui.custom_icons = {
        "alert": "icon-alert",
        "building": "icon-building",
        "contact": "icon-contact",
        "incident": "icon-incident",
        "resource": "icon-wrench",
        "tasks": "icon-tasks",
    }

    # Uncomment to show a default cancel button in standalone create/update forms
    settings.ui.default_cancel_button = True

    # Uncomment to disable responsive behavior of datatables
    # - Disabled until tested
    settings.ui.datatables_responsive = False

    #settings.gis.map_height = 600
    #settings.gis.map_width = 854

    # -----------------------------------------------------------------------------
    # L10n (Localization) settings
    settings.L10n.languages = OrderedDict([
        ("en", "English"),
    ])
    # Default Language
    settings.L10n.default_language = "en"
    # Default timezone for users
    settings.L10n.utc_offset = "UTC -0800"
    # Unsortable 'pretty' date format
    settings.L10n.date_format = "%b %d %Y"
    # Number formats (defaults to ISO 31-0)
    # Decimal separator for numbers (defaults to ,)
    settings.L10n.decimal_separator = "."
    # Thousands separator for numbers (defaults to space)
    settings.L10n.thousands_separator = ","
    # Default Country Code for telephone numbers
    settings.L10n.default_country_code = 1
    # Enable this to change the label for 'Mobile Phone'
    settings.ui.label_mobile_phone = "Cell Phone"
    # Enable this to change the label for 'Postcode'
    settings.ui.label_postcode = "ZIP Code"

    settings.msg.require_international_phone_numbers = False
    # PDF to Letter
    settings.base.paper_size = T("Letter")

    # Uncomment this to Translate CMS Series Names
    # - we want this on when running s3translate but off in normal usage as we use the English names to lookup icons in render_posts
    #settings.L10n.translate_cms_series = True
    # Uncomment this to Translate Location Names
    #settings.L10n.translate_gis_location = True

    # -----------------------------------------------------------------------------
    # GIS settings
    # Restrict the Location Selector to just certain countries
    settings.gis.countries = ("US",)
    # Levels for the LocationSelector
    levels = ("L1", "L2", "L3")

    # Uncomment to pass Addresses imported from CSV to a Geocoder to try and automate Lat/Lon
    #settings.gis.geocode_imported_addresses = "google"

    # Until we add support to S3LocationSelector to set dropdowns from LatLons
    settings.gis.check_within_parent_boundaries = False
    # GeoNames username
    settings.gis.geonames_username = "mcop"
    # Uncomment to hide Layer Properties tool
    #settings.gis.layer_properties = False
    # Uncomment to display the Map Legend as a floating DIV
    settings.gis.legend = "float"
    # Uncomment to prevent showing LatLon in Location Represents
    settings.gis.location_represent_address_only = "icon"
    # Resources which can be directly added to the main map
    settings.gis.poi_create_resources = None

    # -----------------------------------------------------------------------------
    # Module settings
    # Uncomment to customise the list of options for the Priority of a Task.
    # NB Be very cautious about doing this (see docstring in modules/s3cfg.py)
    # MCOP sets these to match Wrike
    settings.project.task_priority_opts = {2: T("High"),
                                           3: T("Normal"),
                                           4: T("Low")
                                           }
    # Uncomment to customise the list of options for the Status of a Task.
    # NB Be very cautious about doing this (see docstring in modules/s3cfg.py)
    # MCOP sets these to match Wrike
    settings.project.task_status_opts = {2: T("Active"),
                                         6: T("Deferred"),
                                         7: T("Cancelled"),
                                        12: T("Completed"),
                                        }
    # -----------------------------------------------------------------------------
    # Enable this for a UN-style deployment
    #settings.ui.cluster = True
    # Enable this to use the label 'Camp' instead of 'Shelter'
    #settings.ui.camp = True

    # -----------------------------------------------------------------------------
    # Uncomment to restrict the export formats available
    #settings.ui.export_formats = ("xls",)

    settings.ui.update_label = "Edit"
    # -----------------------------------------------------------------------------
    # Mariner CommandBridge resource identifiers
    settings.sync.mcb_resource_identifiers = {"event_incident": "802017D4-08D1-40EA-A03D-4FCFC26883A4",
                                              "project_task": "06831BE6-7B49-47F0-80CD-5FB27DEEC330",
                                              "cms_post": "A6E68F53-72B8-415A-A50F-BB26D363CD30",
                                              }
    # Mariner CommandBridge domain identifiers
    settings.sync.mcb_domain_identifiers = {"sahana": "9197B3DC-07DD-4922-96CA-9B6D8A1FC2D2",
                                            "wrike": "69A069D9-23E8-422D-BB18-2A3A92FE291C",
                                            }
    # -----------------------------------------------------------------------------
    # Disable rheaders
    def customise_no_rheader_controller(**attr):
        # Remove rheader
        attr["rheader"] = None
        return attr

    settings.customise_org_facility_controller = customise_no_rheader_controller
    settings.customise_org_organisation_controller = customise_no_rheader_controller
    settings.customise_org_resource_controller = customise_no_rheader_controller

    # -----------------------------------------------------------------------------
    # Summary Pages
    settings.ui.summary = [#{"common": True,
                           # "name": "cms",
                           # "widgets": [{"method": "cms"}]
                           # },
                           {"common": True,
                            "name": "add",
                            "widgets": [{"method": "create"}],
                            },
                           {"name": "table",
                            "label": "Table",
                            "widgets": [{"method": "datatable"}]
                            },
                           {"name": "map",
                            "label": "Map",
                            "widgets": [{"method": "map", "ajax_init": True}],
                            },
                           {"name": "charts",
                            "label": "Reports",
                            "widgets": [{"method": "report", "ajax_init": True}]
                            },
                           ]

    settings.search.filter_manager = False

    # =============================================================================
    # Customise Resources

    # -----------------------------------------------------------------------------
    # Alerts (cms_post)
    # -----------------------------------------------------------------------------
    def cms_post_age(row):
        """
            The age of the post
            - used for colour-coding markers of Alerts
        """

        if hasattr(row, "cms_post"):
            row = row.cms_post
        try:
            date = row.date
        except:
            # not available
            return current.messages["NONE"]

        now = current.request.utcnow
        age = now - date
        if age < timedelta(days=2):
            return 1
        elif age < timedelta(days=7):
            return 2
        else:
            return 3

    # -----------------------------------------------------------------------------
    def customise_cms_post_controller(**attr):

        # Make GeoJSON output smaller
        current.s3db.gis_location.gis_feature_type.represent = None

        # Remove rheader
        attr["rheader"] = None
        return attr

    settings.customise_cms_post_controller = customise_cms_post_controller

    def customise_cms_post_resource(r, tablename):
        """
            Customise cms_post resource
            - CRUD Strings
            - Datatable
            - Fields
            - Form
            Runs after controller customisation
            But runs before prep
        """

        s3 = current.response.s3
        db = current.db
        s3db = current.s3db
        table = s3db.cms_post

        s3.dl_pagelength = 12
        list_id = r.get_vars.get("list_id", None)
        if list_id != "cms_post_datalist":
            # Default page, not homepage
            s3.dl_rowsize = 2

        #from s3 import FS
        #s3.filter = FS("series_id$name").belongs(["Alert"])

        s3.crud_strings["cms_post"] = Storage(
            label_create = T("Add"),
            title_display = T("Alert Details"),
            title_list = T("Alerts"),
            title_update = T("Edit Alert"),
            label_list_button = T("List Alerts"),
            label_delete_button = T("Delete Alert"),
            msg_record_created = T("Alert added"),
            msg_record_modified = T("Alert updated"),
            msg_record_deleted = T("Alert deleted"),
            msg_list_empty = T("No Alerts currently registered"))

        # CRUD Form
        from s3 import IS_LOCATION, S3LocationSelector
        table.location_id.requires = IS_LOCATION()
        table.location_id.widget = S3LocationSelector(levels=levels,
                                                      show_address=True,
                                                      show_map=True,
                                                      points = True,
                                                      polygons = True,
                                                      )
        # Don't add new Locations here
        table.location_id.comment = None

        #table.series_id.readable = table.series_id.writable = True
        #table.series_id.label = T("Type")
        stable = s3db.cms_series
        try:
            series_id = db(stable.name == "Alert").select(stable.id,
                                                          limitby=(0, 1)
                                                          ).first().id
            table.series_id.default = series_id
        except:
            # No suitable prepop
            pass

        table.body.label = T("Description")
        table.body.widget = None

        from s3 import S3SQLCustomForm, S3SQLInlineComponent
        crud_fields = ["date",
                       #"series_id",
                       "body",
                       "location_id",
                       #S3SQLInlineComponent(
                       #    "document",
                       #    name = "file",
                       #    label = T("Files"),
                       #    fields = [("", "file"),
                       #              #"comments",
                       #              ],
                       #    ),
                       ]

        incident_id = r.get_vars.get("~.(incident)", None)
        if incident_id:
            # Coming from Profile page
            # Default location to Incident Location
            itable = s3db.event_incident
            incident = db(itable.id == incident_id).select(itable.location_id,
                                                           limitby=(0, 1)
                                                           ).first()
            if incident:
                table.location_id.default = incident.location_id

            # Add link onaccept
            def create_onaccept(form):
                current.s3db.event_post.insert(incident_id=incident_id,
                                               post_id=form.vars.id)

            s3db.configure("cms_post",
                           create_onaccept = create_onaccept,
                           )
        else:
            # Insert into Form
            crud_fields.insert(0, S3SQLInlineComponent("incident_post",
                                                       label = T("Incident"),
                                                       fields = [("", "incident_id")],
                                                       multiple = False,
                                                       ))

        crud_form = S3SQLCustomForm(*crud_fields)

        from s3 import S3OptionsFilter
        filter_widgets = s3db.get_config("cms_post", "filter_widgets")
        # Remove the Type filter
        # @ToDo: More robust way to identify it
        del filter_widgets[1]
        filter_widgets.insert(1, S3OptionsFilter("incident_post.incident_id"))

        # Return to List view after create/update/delete
        # We do all this in Popups
        url_next = URL(c="cms", f="post", args="datalist")

        # Adapt list fields for cms_post_list_layout?
        #if r.method == "datalist":
        #    list_fields = r.resource.get_config("list_fields")
        #    list_fields.extend(["event_post.event_id",
        #                        "event_post.incident_id",
        #                        "series_id",
        #                        ])

        s3db.configure("cms_post",
                       create_next = url_next,
                       crud_form = crud_form,
                       delete_next = url_next,
                       update_next = url_next,
                       )

        if r.representation == "geojson":
            # Add Virtual field to allow colour-coding by age
            from s3dal import Field
            table.age = Field.Method("age", cms_post_age)

    settings.customise_cms_post_resource = customise_cms_post_resource

    # -----------------------------------------------------------------------------
    # Incidents (event_incident)
    # -----------------------------------------------------------------------------
    def open_incident_filter(selector, tablename=None):
        """
            Default filter for Incidents (callback)
        """

        return [False]

    # -----------------------------------------------------------------------------
    def customise_event_incident_controller(**attr):

        if "summary" in current.request.args:
            settings.gis.legend = None
            # Not working
            from s3 import s3_set_default_filter
            s3_set_default_filter("~.closed",
                                  open_incident_filter,
                                  tablename = "event_incident")

        # Make GeoJSON output smaller
        current.s3db.gis_location.gis_feature_type.represent = None

        s3 = current.response.s3

        # 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):
                actions = [dict(label=str(T("Open")),
                                _class="action-btn",
                                url=URL(c="event", f="incident",
                                        args=["[id]", "profile"])),
                           ]
                s3.actions = actions

            return output
        s3.postp = custom_postp

        # Remove RHeader
        attr["rheader"] = None
        return attr

    settings.customise_event_incident_controller = customise_event_incident_controller

    # -----------------------------------------------------------------------------
    def customise_event_incident_resource(r, tablename):
        """
            Customise org_resource resource
            - Customize Fi
            - List Fields
            - Form
            - Filter Widgets
            Runs after controller customisation
            But runs before prep
        """

        s3db = current.s3db
        table = s3db[tablename]
        crud_strings = current.response.s3.crud_strings

        # Enable 'Lead Organisation' field
        table.organisation_id.readable = table.organisation_id.writable = True

        if r.interactive:
            table.zero_hour.label = T("Date")
            table.comments.label = T("Description")

            crud_strings["event_incident"].label_delete_button = T("Delete Incident")

        list_fields = ["zero_hour",
                       "name",
                       "location_id",
                       "comments",
                       "organisation_id",
                       "closed",
                       ]

        # Custom Form
        location_id_field = table.location_id
        # Don't add new Locations here
        location_id_field.comment = None

        #from gluon.validators import IS_EMPTY_OR
        #table.organisation_id.requires = IS_EMPTY_OR(table.organisation_id.requires)
        from s3 import S3SQLCustomForm, S3SQLInlineComponent
        crud_fields = ["zero_hour",
                       "name",
                       "location_id",
                       "comments",
                       "organisation_id",
                       S3SQLInlineComponent(
                           "document",
                           name = "file",
                           label = T("Documents"),
                           fields = [("", "file"),
                                     #"comments",
                                     ],
                           ),
                       ]
        if r.method != "create":
            crud_fields.append("closed")
        crud_form = S3SQLCustomForm(*crud_fields)

        from s3 import S3TextFilter, S3OptionsFilter, S3LocationFilter
        filter_widgets = [S3TextFilter(["name",
                                        "comments"
                                        ],
                                       #label = T("Description"),
                                       label = T("Search"),
                                       _class = "filter-search",
                                       ),
                          S3OptionsFilter("closed",
                                          cols = 2,
                                          ),
                          S3LocationFilter("location_id",
                                           #label = T("Location"),
                                           levels = levels,
                                           ),
                          #S3OptionsFilter("status_id",
                          #                label = T("Status"),
                          #                # @ToDo: Introspect cols
                          #                cols = 3,
                          #                ),
                          S3OptionsFilter("organisation_id",
                                          represent = "%(name)s",
                                          ),
                          ]

        url_next = URL(c="event", f="incident", args=["[id]", "profile"])

        s3db.configure("event_incident",
                       create_next = url_next,
                       crud_form = crud_form,
                       delete_next = URL(c="event", f="incident", args="summary"),
                       filter_widgets = filter_widgets,
                       list_fields = list_fields,
                       update_next = url_next,
                       )

        if r.method == "profile" and r.tablename == "event_incident":
            # Customise tables used by widgets
            customise_project_task_resource(r, "project_task")

            from s3 import FS
            map_widget = dict(label = "Map",
                              type = "map",
                              context = "incident",
                              icon = "icon-map",
                              # Tall/thin fits Puget Sound best
                              height = 600,
                              width = 200,
                              colspan = 1,
                              )
            alerts_widget = dict(label = "Alerts",
                                 label_create = "Create Alert",
                                 type = "datalist",
                                 tablename = "cms_post",
                                 context = "incident",
                                 # Only show Active Alerts
                                 filter = FS("expired") == False,
                                 icon = "alert",
                                 colspan = 1,
                                 layer = "Alerts",
                                 #list_layout = s3db.cms_post_list_layout,
                                 )
            resources_widget = dict(label = "Resources",
                                    label_create = "Add Resource",
                                    type = "datalist",
                                    tablename = "event_resource",
                                    context = "incident",
                                    #filter = FS("status").belongs(event_resource_active_statuses),
                                    icon = "resource",
                                    colspan = 1,
                                    #list_layout = s3db.event_resource_list_layout,
                                    )
            tasks_widget = dict(label = "Tasks",
                                label_create = "Create Task",
                                type = "datalist",
                                tablename = "project_task",
                                context = "incident",
                                # Only show Active Tasks
                                filter = FS("status").belongs(s3db.project_task_active_statuses),
                                icon = "tasks",
                                colspan = 1,
                                #list_layout = s3db.project_task_list_layout,
                                )
            record = r.record
            record_id = record.id
            record_name = record.name
            title = "%s: %s" % (T("Incident"), record_name)
            marker = current.gis.get_marker(controller = "event",
                                            function = "incident")
            layer = dict(name = record_name,
                         id = "profile-header-%s-%s" % (tablename, record_id),
                         active = True,
                         tablename = tablename,
                         url = "/%s/event/incident.geojson?incident.id=%s" % \
                            (r.application, record_id),
                         marker = marker,
                         )
            if current.auth.s3_has_permission("update", table, record_id=record_id):
                edit_btn = A(I(_class="icon icon-edit"),
                             _href=URL(c="event", f="incident",
                                       args=[record_id, "update.popup"],
                                       vars={"refresh": "datalist"}),
                             _class="s3_modal",
                             _title=s3.crud_strings["event_incident"].title_update,
                             )
            else:
                edit_btn = ""
            # Dropdown of available documents
            # @ToDo: Use resource.components instead of DAL for security (not required here but good practise)
            dtable = s3db.doc_document
            rows = current.db(dtable.doc_id == r.record.doc_id).select(dtable.file)
            documents = [row.file for row in rows]
            if documents:
                doc_list = UL(_class="dropdown-menu",
                              _role="menu",
                              )
                retrieve = dtable.file.retrieve
                for doc in documents:
                    try:
                        doc_name = retrieve(doc)[0]
                    except (IOError, TypeError):
                        doc_name = current.messages["NONE"]
                    doc_url = URL(c="default", f="download",
                                  args=[doc])
                    doc_item = LI(A(I(_class="icon-file"),
                                    " ",
                                    doc_name,
                                    _href=doc_url,
                                    ),
                                  _role="menuitem",
                                  )
                    doc_list.append(doc_item)
                docs = DIV(A(I(_class="icon-paper-clip"),
                             SPAN(_class="caret"),
                             _class="btn dropdown-toggle",
                             _href="#",
                             **{"_data-toggle": "dropdown"}
                             ),
                           doc_list,
                           _class="btn-group attachments dropdown pull-right",
                           )
            else:
                docs = ""
            s3db.configure("event_incident",
                           profile_title = title,
                           profile_header = DIV(edit_btn,
                                                H2(title),
                                                docs,
                                                _class="profile-header",
                                                ),
                           profile_layers = (layer,),
                           profile_widgets = (alerts_widget,
                                              resources_widget,
                                              tasks_widget,
                                              map_widget,
                                              ),
                           profile_cols = 4
                           )

    settings.customise_event_incident_resource = customise_event_incident_resource

    # -----------------------------------------------------------------------------
    # Facilities (org_facility)
    # -----------------------------------------------------------------------------
    def customise_org_facility_resource(r, tablename):
        """
            Customise org_resource resource
            - CRUD Strings
            - List Fields
            - Form
            - Report Options
            Runs after controller customisation
            But runs before prep
        """

        s3 = current.response.s3
        s3db = current.s3db
        table = s3db.org_facility

        s3.crud_strings[tablename] = Storage(
            label_create = T("Add"),
            title_display = T("Facility Details"),
            title_list = T("Facilities"),
            title_update = T("Edit Facility Details"),
            label_list_button = T("List Facilities"),
            label_delete_button = T("Delete Facility"),
            msg_record_created = T("Facility added"),
            msg_record_modified = T("Facility details updated"),
            msg_record_deleted = T("Facility deleted"),
            msg_list_empty = T("No Facilities currently registered"))

        from s3 import IS_LOCATION, S3LocationSelector
        location_id_field = table.location_id
        location_id_field.requires = IS_LOCATION()
        location_id_field.widget = S3LocationSelector(levels=levels,
                                                      show_address=True,
                                                      show_map=True,
                                                      )
        # Don't add new Locations here
        location_id_field.comment = None

        list_fields = ["name",
                       "organisation_id",
                       #(T("Type"), "facility_type.name"),
                       "location_id",
                       "phone1",
                       "comments",
                       ]

        from s3 import S3SQLCustomForm
        crud_form = S3SQLCustomForm(*list_fields)

        # Report options
        report_fields = ["organisation_id",
                         ]

        report_options = Storage(rows = report_fields,
                                 cols = report_fields,
                                 fact = ["count(id)"
                                         ],
                                 defaults=Storage(rows = "organisation_id",
                                                  cols = "",
                                                  fact = "count(id)",
                                                  totals = True,
                                                  chart = "barchart:rows",
                                                  #table = "collapse",
                                                  )
                                 )

        url_next = URL(c="org", f="facility", args="summary")

        s3db.configure("org_facility",
                       create_next = url_next,
                       crud_form = crud_form,
                       delete_next = url_next,
                       list_fields = list_fields,
                       list_layout = render_facilities,
                       report_options = report_options,
                       summary = settings.ui.summary,
                       update_next = url_next,
                       )

        if r.method == "summary":
            settings.gis.legend = None

    settings.customise_org_facility_resource = customise_org_facility_resource

    # -----------------------------------------------------------------------------
    # Stakeholders (org_organisation)
    # -----------------------------------------------------------------------------
    def customise_org_organisation_resource(r, tablename):
        """
            Customise org_organisation resource
            - List Fields
            - CRUD Strings
            - Form
            - Filter
            Runs after controller customisation
            But runs before prep
        """
        # Load normal Model
        s3db = current.s3db
        table = s3db.org_organisation

        list_fields = ["id",
                       "name",
                       "logo",
                       "phone",
                       "website",
                       ]

        if r.interactive:
            # Labels
            table.comments.label = T("Description")

            s3.crud_strings["org_organisation"] = Storage(
                label_create = T("Add"),
                title_display = T("Stakeholder Details"),
                title_list = T("Stakeholders"),
                title_update = T("Edit Stakeholder"),
                label_list_button = T("List Stakeholders"),
                label_delete_button = T("Delete Stakeholder"),
                msg_record_created = T("Stakeholder added"),
                msg_record_modified = T("Stakeholder updated"),
                msg_record_deleted = T("Stakeholder deleted"),
                msg_list_empty = T("No Stakeholders currently registered"))

            from s3 import S3SQLCustomForm, S3SQLInlineLink
            crud_form = S3SQLCustomForm("id",
                                        "name",
                                        S3SQLInlineLink(
                                            "organisation_type",
                                            field = "organisation_type_id",
                                            label = T("Type"),
                                            multiple = False,
                                            #widget = "hierarchy",
                                        ),
                                        "logo",
                                        "phone",
                                        "website",
                                        "comments",
                                        )

            s3db.configure("org_organisation",
                           crud_form = crud_form,
                           )

            if r.method == "datalist":
                # Stakeholder selection page
                # 2-column datalist, 6 rows per page
                s3.dl_pagelength = 12
                s3.dl_rowsize = 3

                from s3 import S3TextFilter, S3OptionsFilter
                filter_widgets = [S3TextFilter(["name",
                                                "acronym",
                                                "website",
                                                "comments",
                                                ],
                                              label = T("Search")),
                                  S3OptionsFilter("organisation_organisation_type.organisation_type_id",
                                                  label = T("Type"),
                                                  ),
                                  ]

                s3db.configure("org_organisation",
                               filter_widgets = filter_widgets,
                               )

            if r.method == "profile":
                # Ensure the correct list_fields are set
                # @ToDo: Have profile method call these automatically
                customise_pr_person_resource(r, "pr_person")
                customise_event_incident_resource(r, "event_incident")
                # Customise tables used by widgets
                #customise_cms_post_fields()
                #customise_hrm_human_resource_fields()
                #customise_org_office_fields()
                s3db.org_customise_org_resource_fields("profile")

                #from s3 import FS
                contacts_widget = dict(label = "Directory",
                                       label_create = "Create Contact",
                                       type = "datalist",
                                       tablename = "hrm_human_resource",
                                       context = "organisation",
                                       create_controller = "pr",
                                       create_function = "person",
                                       icon = "contact",
                                       show_on_map = False, # Since they will show within Offices
                                       list_layout = render_contacts,
                                       )
                #map_widget = dict(label = "Map",
                #                  type = "map",
                #                  context = "organisation",
                #                  icon = "icon-map",
                #                  height = 383,
                #                  width = 568,
                #                  )
                facilities_widget = dict(label = "Facilities",
                                         label_create = "Create Facility",
                                         type = "datalist",
                                         tablename = "org_facility",
                                         context = "organisation",
                                         icon = "building",
                                         layer = "Facilities",
                                         # provided by Catalogue Layer
                                         #marker = "office",
                                         list_layout = render_facilities,
                                         )
                resources_widget = dict(label = "Resources",
                                        label_create = "Create Resource",
                                        type = "datalist",
                                        tablename = "org_resource",
                                        context = "organisation",
                                        icon = "resource",
                                        show_on_map = False, # No Marker yet & only show at L1-level anyway
                                        #list_layout = s3db.org_resource_list_layout,
                                        )
                incidents_widget = dict(label = "Incidents",
                                        label_create = "Create Incident",
                                        type = "datalist",
                                        tablename = "event_incident",
                                        context = "organisation",
                                        icon = "incident",
                                        show_on_map = False, # No Marker yet & only show at L1-level anyway
                                        #list_layout = s3db.event_incident_list_layout,
                                        )
                record = r.record
                title = "%s : %s" % (s3.crud_strings["org_organisation"].title_list, record.name)
                if record.logo:
                    logo = URL(c="default", f="download", args=[record.logo])
                else:
                    logo = ""
                s3db.configure("org_organisation",
                               profile_title = title,
                               profile_header = DIV(A(IMG(_class="media-object",
                                                          _src=logo,
                                                          ),
                                                      _class="pull-left",
                                                      #_href=org_url,
                                                      ),
                                                    H2(title),
                                                    _class="profile-header",
                                                    ),
                               profile_widgets = [contacts_widget,
                                                  #map_widget,
                                                  facilities_widget,
                                                  resources_widget,
                                                  incidents_widget,
                                                  #activities_widget,
                                                  #reports_widget,
                                                  #assessments_widget,
                                                  ],
                               #profile_cols = 3
                               )

        # Return to List view after create/update/delete (unless done via Modal)
        url_next = URL(c="org", f="organisation", args="datalist")

        s3db.configure("org_organisation",
                       create_next = url_next,
                       delete_next = url_next,
                       # We want the Create form to be in a modal, not inline, for consistency
                       #listadd = False,
                       list_fields = list_fields,
                       update_next = url_next,
                       )

    settings.customise_org_organisation_resource = customise_org_organisation_resource

    # -----------------------------------------------------------------------------
    # Resource Inventory (org_resource)
    # -----------------------------------------------------------------------------
    def customise_org_resource_resource(r, tablename):
        """
            Customise org_resource resource
            - Fields
            - Filter
            Runs after controller customisation
            But runs before prep
        """

        if r.representation == "geojson":
            # Make GeoJSON output smaller
            current.s3db.gis_location.gis_feature_type.represent = None

        elif r.interactive:
            s3 = current.response.s3
            s3db = current.s3db
            table = s3db.org_resource

            s3.crud_strings[tablename] = Storage(
                label_create = T("Add"),
                title_display = T("Inventory Resource"),
                title_list = T("Resource Inventory"),
                title_update = T("Edit Inventory Resource"),
                label_list_button = T("Resource Inventory"),
                label_delete_button = T("Delete Inventory Resource"),
                msg_record_created = T("Inventory Resource added"),
                msg_record_modified = T("Inventory Resource updated"),
                msg_record_deleted = T("Inventory Resource deleted"),
                msg_list_empty = T("No Resources in Inventory"))

            location_field = table.location_id
            # Filter from a Profile page?
            # If so, then default the fields we know
            get_vars = current.request.get_vars
            location_id = get_vars.get("~.(location)", None)
            organisation_id = get_vars.get("~.(organisation)", None)
            if organisation_id:
                org_field = table.organisation_id
                org_field.default = organisation_id
                org_field.readable = org_field.writable = False
            if location_id:
                location_field.default = location_id
                # We still want to be able to specify a precise location
                #location_field.readable = location_field.writable = False
            from s3 import IS_LOCATION, S3LocationSelector
            location_field.requires = IS_LOCATION()
            location_field.widget = S3LocationSelector(levels=levels,
                                                       show_address=True,
                                                       show_map=True,
                                                       )

            # Don't add new Locations here
            location_field.comment = None

            s3db.org_customise_org_resource_fields(r.method)

            # Configure fields
            #table.site_id.readable = table.site_id.readable = False
            #location_field.label = T("District")

            # Return to Sumamry view after create/update/delete (unless done via Modal)
            url_next = URL(c="org", f="resource", args="summary")

            s3db.configure("org_resource",
                           create_next = url_next,
                           delete_next = url_next,
                           # Don't include a Create form in 'More' popups
                           listadd = False if r.method=="datalist" else True,
                           update_next = url_next,
                           )

            # This is awful in Popups & inconsistent in dataTable view (People/Documents don't have this & it breaks the styling of the main Save button)
            s3.cancel = URL(c="org", f="resource")

        if r.method == "summary":
            settings.gis.legend = None

    settings.customise_org_resource_resource = customise_org_resource_resource

    # -----------------------------------------------------------------------------
    def customise_event_resource_resource(r, tablename):
        """
            Customise org_resource resource
            - Fields
            - Filter
            Runs after controller customisation
            But runs before prep
        """

        if r.representation == "geojson":
            # Make GeoJSON output smaller
            current.s3db.gis_location.gis_feature_type.represent = None

        elif r.interactive:
            current.response.s3.crud_strings[tablename] = Storage(
                label_create = T("Add"),
                title_display = T("Resource Responding"),
                title_list = T("Resources Responding"),
                title_update = T("Edit Resource Responding"),
                label_list_button = T("Resources Responding"),
                label_delete_button = T("Delete Resource Responding"),
                msg_record_created = T("Resource Responding added"),
                msg_record_modified = T("Resource Responding updated"),
                msg_record_deleted = T("Resource Responding deleted"),
                msg_list_empty = T("No Resources Responding"))

        if r.method == "summary":
            settings.gis.legend = None

    settings.customise_event_resource_resource = customise_event_resource_resource

    # -----------------------------------------------------------------------------
    # Tasks (project_task)
    # -----------------------------------------------------------------------------
    def active_status_filter(selector, tablename=None):
        """
            Default filter for Tasks (callback)
        """

        return current.s3db.project_task_active_statuses

    # -----------------------------------------------------------------------------
    def customise_project_task_controller(**attr):

        if "summary" in current.request.args:
            settings.gis.legend = None
            # Not working
            from s3 import s3_set_default_filter
            s3_set_default_filter("~.status",
                                  active_status_filter,
                                  tablename = "project_task")

        # Make GeoJSON output smaller
        current.s3db.gis_location.gis_feature_type.represent = None

        # Remove rheader
        attr["rheader"] = None
        return attr

    settings.customise_project_task_controller = customise_project_task_controller

    # -----------------------------------------------------------------------------
    def customise_project_task_resource(r, tablename):
        """
            Customise org_resource resource
            - List Fields
            - Fields
            - Form
            - Filter Widgets
            - Report Options
            Runs after controller customisation
            But runs before prep
        """

        s3db = current.s3db
        table = s3db.project_task

        if r.tablename == "event_incident" and r.method == "profile":
            # Set list_fields for renderer (project_task_list_layout)
            list_fields = ["name",
                           "description",
                           "location_id",
                           "date_due",
                           "pe_id",
                           "incident.incident_id",
                           #"organisation_id$logo",
                           "pe_id",
                           "source_url",
                           "modified_by",
                           "status",
                           "priority",
                           ]
        else:
            list_fields = ["id",
                           "status",
                           "priority",
                           "incident.incident_id",
                           (T("Task"), "name"),
                           "location_id",
                           "date_due",
                           (T("Wrike Permalink"), "source_url"),
                           ]

        # Custom Form
        table.name.label = T("Name")
        table.description.label = T("Description")
        table.description.comment = None
        table.location_id.readable = table.location_id.writable = True
        from s3 import S3SQLCustomForm, S3SQLInlineComponent
        crud_fields = ["source_url",
                       "status",
                       "priority",
                       "name",
                       "description",
                       "pe_id",
                       "date_due",
                       "location_id",
                       ]

        incident_id = r.get_vars.get("~.(incident)", None)
        if incident_id:
            # Coming from Profile page
            # Add link onaccept
            def create_onaccept(form):
                current.s3db.event_task.insert(incident_id=incident_id,
                                               task_id=form.vars.id)

            s3db.configure("project_task",
                           create_onaccept = create_onaccept,
                           )
        else:
            # Insert into Form
            crud_fields.insert(0, S3SQLInlineComponent("incident",
                                                       label = T("Incident"),
                                                       fields = [("", "incident_id")],
                                                       multiple = False,
                                                       ))

        if (r.method == None or r.method == "update") and \
           r.record and r.record.source_url:
            # Task imported from Wrike
            # - lock all fields which should only be edited within Wrike
            #crud_fields.insert(0, "source_url")
            current.s3db.event_task.incident_id.writable = False
            for fieldname in ["source_url",
                              "status",
                              "priority",
                              "name",
                              "description",
                              "pe_id",
                              "date_due"]:
                table[fieldname].writable = False

        crud_form = S3SQLCustomForm(*crud_fields)

        # Filter Widgets
        from s3 import S3OptionsFilter
        filter_widgets = s3db.get_config("project_task", "filter_widgets")
        filter_widgets.insert(2, S3OptionsFilter("incident.incident_id"))

        # Report options
        report_fields = ["status",
                         "priority",
                         "incident.incident_id",
                         "pe_id",
                         "location_id",
                         ]

        report_options = Storage(rows = report_fields,
                                 cols = report_fields,
                                 fact = ["count(name)"],
                                 defaults=Storage(rows = "status",
                                                  cols = "priority",
                                                  fact = "count(name)",
                                                  totals = True,
                                                  chart = "barchart:rows",
                                                  )
                                 )

        url_next = URL(c="project", f="task", args="summary")

        s3db.configure("project_task",
                       create_next = url_next,
                       crud_form = crud_form,
                       delete_next = url_next,
                       list_fields = list_fields,
                       onvalidation = None, # don't check pe_id if status Active
                       orderby = "project_task.date_due asc",
                       report_options = report_options,
                       update_next = url_next,
                       )

    settings.customise_project_task_resource = customise_project_task_resource

    # -----------------------------------------------------------------------------
    # Contacts (pr_person)
    # -----------------------------------------------------------------------------
    def customise_pr_person_controller(**attr):

        s3 = current.response.s3

        # 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):
                actions = [dict(label=str(T("Open")),
                                _class="action-btn",
                                url=URL(c="pr", f="person",
                                        args=["[id]", "read"]))
                           ]
                s3.actions = actions

                if "form" in output:
                    output["form"].add_class("pr_person")
                elif "item" in output and hasattr(output["item"], "add_class"):
                    output["item"].add_class("pr_person")

            return output
        s3.postp = custom_postp

        # Remove RHeader
        attr["rheader"] = None
        return attr

    settings.customise_pr_person_controller = customise_pr_person_controller

    # -----------------------------------------------------------------------------
    def customise_pr_person_resource(r, tablename):
        """
            Customise org_resource resource
            - List Fields
            - Fields
            - Form
            - Filter Widgets
            - Report Options
            Runs after controller customisation
            But runs before prep
        """

        s3db = current.s3db
        request = current.request
        s3 = current.response.s3

        tablename = "pr_person"

        # CRUD Strings
        s3.crud_strings[tablename] = Storage(
            label_create = T("Add"),
            title_display = T("Contact Details"),
            title_list = T("Contact Directory"),
            title_update = T("Edit Contact Details"),
            label_list_button = T("List Contacts"),
            label_delete_button = T("Delete Contact"),
            msg_record_created = T("Contact added"),
            msg_record_modified = T("Contact details updated"),
            msg_record_deleted = T("Contact deleted"),
            msg_list_empty = T("No Contacts currently registered"))

        if r.method == "validate":
            # Can't validate image without the file
            image_field = s3db.pr_image.image
            image_field.requires = None

        MOBILE = settings.get_ui_label_mobile_phone()
        EMAIL = T("Email")

        htable = s3db.hrm_human_resource
        htable.organisation_id.widget = None
        site_field = htable.site_id
        from s3 import S3Represent, IS_ONE_OF
        represent = S3Represent(lookup="org_site")
        site_field.label = T("Facility")
        site_field.represent = represent
        site_field.requires = IS_ONE_OF(current.db, "org_site.site_id",
                                        represent,
                                        orderby = "org_site.name")

        from s3layouts import S3AddResourceLink
        site_field.comment = S3AddResourceLink(c="org", f="facility",
                                               vars={"child": "site_id"},
                                               label=T("Create Facility"),
                                               title=T("Facility"),
                                               tooltip=T("If you don't see the Facility in the list, you can add a new one by clicking link 'Create Facility'."))

        # ImageCrop widget doesn't currently work within an Inline Form
        image_field = s3db.pr_image.image
        from gluon.validators import IS_IMAGE
        image_field.requires = IS_IMAGE()
        image_field.widget = None

        hr_fields = ["organisation_id",
                     "job_title_id",
                     "site_id",
                     ]

        # Context from a Profile page?"
        organisation_id = request.get_vars.get("(organisation)", None)
        if organisation_id:
            field = s3db.hrm_human_resource.organisation_id
            field.default = organisation_id
            field.readable = field.writable = False
            hr_fields.remove("organisation_id")

        from s3 import S3SQLCustomForm, S3SQLInlineComponent
        s3_sql_custom_fields = [
                "first_name",
                #"middle_name",
                "last_name",
                S3SQLInlineComponent(
                    "human_resource",
                    name = "human_resource",
                    label = "",
                    multiple = False,
                    fields = hr_fields,
                ),
                S3SQLInlineComponent(
                    "image",
                    name = "image",
                    label = T("Photo"),
                    multiple = False,
                    fields = [("", "image")],
                    filterby = dict(field = "profile",
                                    options=[True]
                                    )
                ),
            ]

        list_fields = ["human_resource.organisation_id",
                       "first_name",
                       #"middle_name",
                       "last_name",
                       (T("Job Title"), "human_resource.job_title_id"),
                       (T("Facility"), "human_resource.site_id"),
                       ]

        # Don't include Email/Phone for unauthenticated users
        if current.auth.is_logged_in():
            list_fields += [(MOBILE, "phone.value"),
                            (EMAIL, "email.value"),
                            ]
            s3_sql_custom_fields.insert(3,
                                        S3SQLInlineComponent(
                                        "contact",
                                        name = "phone",
                                        label = MOBILE,
                                        multiple = False,
                                        fields = [("", "value")],
                                        filterby = dict(field = "contact_method",
                                                        options = "SMS")),
                                        )
            s3_sql_custom_fields.insert(3,
                                        S3SQLInlineComponent(
                                        "contact",
                                        name = "email",
                                        label = EMAIL,
                                        multiple = False,
                                        fields = [("", "value")],
                                        filterby = dict(field = "contact_method",
                                                        options = "EMAIL")),
                                        )

        crud_form = S3SQLCustomForm(*s3_sql_custom_fields)

        from s3 import S3TextFilter, S3OptionsFilter
        filter_widgets = [S3TextFilter(["pe_label",
                                        "first_name",
                                        "middle_name",
                                        "last_name",
                                        "local_name",
                                        "identity.value",
                                        "human_resource.organisation_id",
                                        "human_resource.job_title_id",
                                        "human_resource.site_id",
                                        ],
                                       label=T("Search"),
                                       ),
                          S3OptionsFilter("human_resource.organisation_id",
                                          ),
                          S3OptionsFilter("human_resource.job_title_id",
                                          ),
                          S3OptionsFilter("human_resource.site_id",
                                          ),
                          ]

        # Return to List view after create/update/delete (unless done via Modal)
        url_next = URL(c="pr", f="person", )

        # Report options
        report_fields = ["organisation_id",
                         ]

        report_options = Storage(rows = report_fields,
                                 cols = report_fields,
                                 fact = ["count(id)"
                                         ],
                                 defaults=Storage(rows = "organisation_id",
                                                  cols = "",
                                                  fact = "count(id)",
                                                  totals = True,
                                                  chart = "barchart:rows",
                                                  #table = "collapse",
                                                  )
                                 )

        s3db.configure(tablename,
                       create_next = url_next,
                       crud_form = crud_form,
                       delete_next = url_next,
                       filter_widgets = filter_widgets,
                       listadd = True,
                       list_fields = list_fields,
                       report_options = report_options,
                       # Don't include a Create form in 'More' popups
                       #listadd = False if r.method=="datalist" else True,
                       #list_layout = render_contacts,
                       update_next = url_next,
                       )

        # HR Fields For dataList Cards
        list_fields = ["person_id",
                       "organisation_id",
                       "site_id$location_id",
                       "site_id$location_id$addr_street",
                       "job_title_id",
                       "email.value",
                       "phone.value",
                       #"modified_by",
                       "modified_on",
                       ]

        s3db.configure("hrm_human_resource",
                       list_fields = list_fields,
                       )

    settings.customise_pr_person_resource = customise_pr_person_resource

    # =============================================================================
    # Custom list_layout renders (Copy & Pasted from DRMP)
    # @ToDo: re-factor

    # -----------------------------------------------------------------------------
    def render_contacts(list_id, item_id, resource, rfields, record):
        """
            Custom dataList item renderer for Contacts on the Profile pages

            @param list_id: the HTML ID of the list
            @param item_id: the HTML ID of the item
            @param resource: the S3Resource to render
            @param rfields: the S3ResourceFields to render
            @param record: the record as dict
        """

        record_id = record["hrm_human_resource.id"]
        item_class = "thumbnail"

        raw = record._row
        #author = record["hrm_human_resource.modified_by"]
        #date = record["hrm_human_resource.modified_on"]
        fullname = record["hrm_human_resource.person_id"]
        job_title = raw["hrm_human_resource.job_title_id"] or ""
        if job_title:
            job_title = "- %s" % record["hrm_human_resource.job_title_id"]
        #organisation = record["hrm_human_resource.organisation_id"]
        organisation_id = raw["hrm_human_resource.organisation_id"]
        #org_url = URL(c="org", f="organisation", args=[organisation_id, "profile"])
        person_id = raw["hrm_human_resource.person_id"]
        #location = record["org_site.location_id"]
        #location_id = raw["org_site.location_id"]
        #location_url = URL(c="gis", f="location",
        #                   args=[location_id, "profile"])
        #address = raw["gis_location.addr_street"] or T("no facility assigned")
        email = raw["pr_email_contact.value"] or T("no email address")
        if isinstance(email, list):
            email = email[0]
        phone = raw["pr_phone_contact.value"] or T("no phone number")
        if isinstance(phone, list):
            phone = phone[0]

        if person_id:
            # Use Personal Avatar
            # @ToDo: Optimise by not doing DB lookups within render, but doing these in the bulk query
            avatar = s3_avatar_represent(person_id,
                                         tablename="pr_person",
                                         _class="media-object")
        else:
            avatar = IMG(_src=URL(c="static", f="img", args="blank-user.gif"),
                         _class="media-object")

        # Edit Bar
        permit = current.auth.s3_has_permission
        table = current.s3db.pr_person
        if permit("update", table, record_id=person_id):
            vars = {"refresh": list_id,
                    "record": record_id,
                    }
            f = current.request.function
            if f == "organisation" and organisation_id:
                vars["(organisation)"] = organisation_id
            edit_url = URL(c="hrm", f="person",
                           args=[person_id, "update.popup"],
                           vars=vars)
            title_update = current.response.s3.crud_strings.hrm_human_resource.title_update
            edit_btn = A(I(" ", _class="icon icon-edit"),
                         _href=edit_url,
                         _class="s3_modal",
                         _title=title_update,
                         )
        else:
            edit_btn = ""
            edit_url = "#"
            title_update = ""
        # Deletions failing due to Integrity Errors
        #if permit("delete", table, record_id=person_id):
        #    delete_btn = A(I(" ", _class="icon icon-remove-sign"),
        #                   _class="dl-item-delete",
        #                   )
        #else:
        delete_btn = ""
        edit_bar = DIV(edit_btn,
                       delete_btn,
                       _class="edit-bar fright",
                       )

        avatar = A(avatar,
                   _href=edit_url,
                   _class="pull-left s3_modal",
                   _title=title_update,
                   )

        # Render the item
        body = TAG[""](P(I(_class="icon-phone"),
                         " ",
                         SPAN(phone),
                         " ",
                         ),
                       P(I(_class="icon-envelope-alt"),
                         " ",
                         SPAN(email),
                         _class="main_contact_ph",
                         ),
                       #P(I(_class="icon-home"),
                       #  " ",
                       #  address,
                       #  _class="main_office-add",
                       #  )
                       )

        item = DIV(DIV(SPAN(fullname,
                            " ",
                            job_title,
                            _class="card-title"),
                       edit_bar,
                       _class="card-header",
                       ),
                   DIV(avatar,
                       DIV(DIV(body,
                               # Organisation only needed if displaying elsewhere than org profile
                               # Author confusing with main contact record
                               #DIV(#author,
                               #    #" - ",
                               #    A(organisation,
                               #      _href=org_url,
                               #      _class="card-organisation",
                               #      ),
                               #    _class="card-person",
                               #    ),
                               _class="media",
                               ),
                           _class="media-body",
                           ),
                       _class="media",
                       ),
                   #docs,
                   _class=item_class,
                   _id=item_id,
                   )

        return item
    # -----------------------------------------------------------------------------
    def render_facilities(list_id, item_id, resource, rfields, record):
        """
            Custom dataList item renderer for Facilities on the Profile pages

            @param list_id: the HTML ID of the list
            @param item_id: the HTML ID of the item
            @param resource: the S3Resource to render
            @param rfields: the S3ResourceFields to render
            @param record: the record as dict
        """

        record_id = record["org_facility.id"]
        item_class = "thumbnail"

        raw = record._row
        name = record["org_facility.name"]
        #author = record["org_facility.modified_by"]
        #date = record["org_facility.modified_on"]
        #organisation = record["org_facility.organisation_id"]
        organisation_id = raw["org_facility.organisation_id"]
        #location = record["org_facility.location_id"]
        #location_id = raw["org_facility.location_id"]
        #location_url = URL(c="gis", f="location",
        #                   args=[location_id, "profile"])
        address = raw["gis_location.addr_street"]
        phone = raw["org_facility.phone1"]
        #facility_type = record["org_facility.facility_type_id"]
        logo = raw["org_organisation.logo"]

        org_url = URL(c="org", f="organisation", args=[organisation_id, "profile"])
        if logo:
            logo = A(IMG(_src=URL(c="default", f="download", args=[logo]),
                         _class="media-object",
                         ),
                     _href=org_url,
                     _class="pull-left",
                     )
        else:
            logo = DIV(IMG(_class="media-object"),
                       _class="pull-left")

        # Edit Bar
        permit = current.auth.s3_has_permission
        table = current.db.org_facility
        if permit("update", table, record_id=record_id):
            vars = {"refresh": list_id,
                    "record": record_id,
                    }
            f = current.request.function
            if f == "organisation" and organisation_id:
                vars["(organisation)"] = organisation_id
            edit_btn = A(I(" ", _class="icon icon-edit"),
                         _href=URL(c="org", f="facility",
                                   args=[record_id, "update.popup"],
                                   vars=vars),
                         _class="s3_modal",
                         _title=current.response.s3.crud_strings.org_facility.title_update,
                         )
        else:
            edit_btn = ""
        if permit("delete", table, record_id=record_id):
            delete_btn = A(I(" ", _class="icon icon-remove-sign"),
                           _class="dl-item-delete",
                           )
        else:
            delete_btn = ""
        edit_bar = DIV(edit_btn,
                       delete_btn,
                       _class="edit-bar fright",
                       )

        # Render the item
        #avatar = logo
        body = TAG[""](#P(I(_class="icon-flag"),
                       #  " ",
                       #  SPAN(facility_type),
                       #  " ",
                       #  _class="main_contact_ph",
                       #  ),
                       P(I(_class="icon-phone"),
                         " ",
                         SPAN(phone or T("no phone number")),
                         " ",
                         ),
                       P(I(_class="icon-home"),
                         " ",
                         address,
                         _class="main_facility-add",
                         ))

        item = DIV(DIV(SPAN(name, _class="card-title"),
                       edit_bar,
                       _class="card-header",
                       ),
                   DIV(#avatar,
                       DIV(DIV(body,
                               DIV(#author,
                                   #" - ",
                                   #A(organisation,
                                   #  _href=org_url,
                                   #  _class="card-organisation",
                                   #  ),
                                   #_class="card-person",
                                   ),
                               _class="media",
                               ),
                           _class="media-body",
                           ),
                       _class="media",
                       ),
                   #docs,
                   _class=item_class,
                   _id=item_id,
                   )

        return item

    # =============================================================================
    # Modules
    # Comment/uncomment modules here to disable/enable them
    settings.modules = OrderedDict([
        # Core modules which shouldn't be disabled
        ("default", Storage(
            name_nice = "Home",
            restricted = False, # Use ACLs to control access to this module
            access = None,      # All Users (inc Anonymous) can see this module in the default menu & access the controller
            module_type = None  # This item is not shown in the menu
        )),
        ("admin", Storage(
            name_nice = "Administration",
            #description = "Site Administration",
            restricted = True,
            access = "|1|",     # Only Administrators can see this module in the default menu & access the controller
            module_type = None  # This item is handled separately for the menu
        )),
        ("appadmin", Storage(
            name_nice = "Administration",
            #description = "Site Administration",
            restricted = True,
            module_type = None  # No Menu
        )),
    #    ("errors", Storage(
    #        name_nice = "Ticket Viewer",
    #        #description = "Needed for Breadcrumbs",
    #        restricted = False,
    #        module_type = None  # No Menu
    #    )),
       ("sync", Storage(
           name_nice = "Synchronization",
           #description = "Synchronization",
           restricted = True,
           access = "|1|",     # Only Administrators can see this module in the default menu & access the controller
           module_type = None  # This item is handled separately for the menu
       )),
        ("translate", Storage(
            name_nice = "Translation Functionality",
            #description = "Selective translation of strings based on module.",
            module_type = None,
        )),
        ("gis", Storage(
            name_nice = "Map",
            #description = "Situation Awareness & Geospatial Analysis",
            restricted = True,
            module_type = 1,     # 1st item in the menu
        )),
        ("pr", Storage(
            name_nice = "Persons",
            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 = None
        )),
        ("org", Storage(
            name_nice = "Organizations",
            #description = 'Lists "who is doing what & where". Allows relief agencies to coordinate their activities',
            restricted = True,
            module_type = None
        )),
        # All modules below here should be possible to disable safely
        ("hrm", Storage(
            name_nice = "Contacts",
            #description = "Human Resources Management",
            restricted = True,
            module_type = None,
        )),
        ("cms", Storage(
                name_nice = "Content Management",
                restricted = True,
                module_type = None,
            )),
        ("event", Storage(
                name_nice = "Event Management",
                restricted = True,
                module_type = None,
            )),
        ("project", Storage(
                name_nice = "Project Management",
                restricted = True,
                module_type = None,
            )),
        ("doc", Storage(
            name_nice = "Documents",
            #description = "A library of digital resources, such as photos, documents and reports",
            restricted = True,
            module_type = None,
        )),
        ("stats", Storage(
            name_nice = "Statistics",
            restricted = True,
            module_type = None
        )),
    ])

# END =========================================================================


text:
import Backbone from "backbone";
import User from "models/User";
import globals from "globals";

export default Backbone.Collection.extend({
    model: User,

    url: globals.API_V2_ROOT + "/users",

    comparator: function(model) {
        var username = model.get("username");
        if (username)
            return model.get("username").toLowerCase();
        return username;
    },

    parse: function(response) {
        this.meta = {
            count: response.count,
            next: response.next,
            previous: response.previous
        };

        return response.results;
    }

});

redpajama_set_name: RedPajamaGithub


text:
// Copyright 2007-2015 Chris Patterson, Dru Sellers, Travis Smith, et. al.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
namespace MassTransit.AutofacIntegration
{
    using Autofac;

    /// <summary>
    /// Delegate to configure a lifetime scope
    /// </summary>
    /// <typeparam name="TId">The container registry identifier type</typeparam>
    /// <param name="scopeId">The scopeId</param>
    /// <param name="containerBuilder">The container builder which configures the lifetime scope</param>
    public delegate void LifetimeScopeConfigurator<in TId>(TId scopeId, ContainerBuilder containerBuilder);
}


text:
import os.path
import time

from collections import namedtuple

from evdev import UInput, UInputError, ecodes
from evdev import util

from .exceptions import DeviceError

# Check for the existence of a "resolve_ecodes_dict" function.
# Need to know if axis options tuples should be altered.
# This is needed to keep the code compatible with python-evdev < 0.6.0.
absInfoUsesValue = hasattr(util, "resolve_ecodes_dict")

BUTTON_MODIFIERS = ("+", "-")

DEFAULT_A2D_DEADZONE = 50
DEFAULT_AXIS_OPTIONS = (0, 0, 255, 0, 5)
DEFAULT_MOUSE_SENSITIVTY = 0.8
DEFAULT_MOUSE_DEADZONE = 5
DEFAULT_SCROLL_REPEAT_DELAY = .250 # Seconds to wait before continual scrolling
DEFAULT_SCROLL_DELAY = .035        # Seconds to wait between scroll events

UInputMapping = namedtuple("UInputMapping",
                           "name bustype vendor product version "
                           "axes axes_options buttons hats keys mouse "
                           "mouse_options")

_mappings = {}

# Add our simulated mousewheel codes
ecodes.REL_WHEELUP = 13      # Unique value for this lib
ecodes.REL_WHEELDOWN = 14    # Ditto

def parse_button(attr):
    if attr[0] in BUTTON_MODIFIERS:
        modifier = attr[0]
        attr = attr[1:]
    else:
        modifier = None

    return (attr, modifier)

def create_mapping(name, description, bustype=0, vendor=0, product=0,
                   version=0, axes={}, axes_options={}, buttons={},
                   hats={}, keys={}, mouse={}, mouse_options={}):
    axes = {getattr(ecodes, k): v for k,v in axes.items()}
    axes_options = {getattr(ecodes, k): v for k,v in axes_options.items()}
    buttons = {getattr(ecodes, k): parse_button(v) for k,v in buttons.items()}
    hats = {getattr(ecodes, k): v for k,v in hats.items()}
    mouse = {getattr(ecodes, k): parse_button(v) for k,v in mouse.items()}

    mapping = UInputMapping(description, bustype, vendor, product, version,
                            axes, axes_options, buttons, hats, keys, mouse,
                            mouse_options)
    _mappings[name] = mapping

# Pre-configued mappings
create_mapping(
    "ds4", "Sony Computer Entertainment Wireless Controller",
    # Bus type,     vendor, product, version
    ecodes.BUS_USB, 1356,   1476,    273,
    # Axes
    {
        "ABS_X":        "left_analog_x",
        "ABS_Y":        "left_analog_y",
        "ABS_Z":        "right_analog_x",
        "ABS_RZ":       "right_analog_y",
        "ABS_RX":       "l2_analog",
        "ABS_RY":       "r2_analog",
        "ABS_THROTTLE": "orientation_roll",
        "ABS_RUDDER":   "orientation_pitch",
        "ABS_WHEEL":    "orientation_yaw",
        "ABS_DISTANCE": "motion_z",
        "ABS_TILT_X":   "motion_x",
        "ABS_TILT_Y":   "motion_y",
    },
    # Axes options
    {
        "ABS_THROTTLE": (0, -16385, 16384, 0, 0),
        "ABS_RUDDER":   (0, -16385, 16384, 0, 0),
        "ABS_WHEEL":    (0, -16385, 16384, 0, 0),
        "ABS_DISTANCE": (0, -32768, 32767, 0, 10),
        "ABS_TILT_X":   (0, -32768, 32767, 0, 10),
        "ABS_TILT_Y":   (0, -32768, 32767, 0, 10),
    },
    # Buttons
    {
        "BTN_TR2":    "button_options",
        "BTN_MODE":   "button_ps",
        "BTN_TL2":    "button_share",
        "BTN_B":      "button_cross",
        "BTN_C":      "button_circle",
        "BTN_A":      "button_square",
        "BTN_X":      "button_triangle",
        "BTN_Y":      "button_l1",
        "BTN_Z":      "button_r1",
        "BTN_TL":     "button_l2",
        "BTN_TR":     "button_r2",
        "BTN_SELECT": "button_l3",
        "BTN_START":  "button_r3",
        "BTN_THUMBL": "button_trackpad"
    },
    # Hats
    {
        "ABS_HAT0X": ("dpad_left", "dpad_right"),
        "ABS_HAT0Y": ("dpad_up", "dpad_down")
    }
)

create_mapping(
    "xboxdrv", "Xbox Gamepad (userspace driver)",
    # Bus type, vendor, product, version
    0,          0,      0,       0,
    # Axes
    {
        "ABS_X":     "left_analog_x",
        "ABS_Y":     "left_analog_y",
        "ABS_RX":    "right_analog_x",
        "ABS_RY":    "right_analog_y",
        "ABS_BRAKE": "l2_analog",
        "ABS_GAS":   "r2_analog"
    },
    # Axes settings
    {},
    #Buttons
    {
        "BTN_START":  "button_options",
        "BTN_MODE":   "button_ps",
        "BTN_SELECT": "button_share",
        "BTN_A":      "button_cross",
        "BTN_B":      "button_circle",
        "BTN_X":      "button_square",
        "BTN_Y":      "button_triangle",
        "BTN_TL":     "button_l1",
        "BTN_TR":     "button_r1",
        "BTN_THUMBL": "button_l3",
        "BTN_THUMBR": "button_r3"
    },
    # Hats
    {
        "ABS_HAT0X": ("dpad_left", "dpad_right"),
        "ABS_HAT0Y": ("dpad_up", "dpad_down")
    }
)

create_mapping(
    "xpad", "Microsoft X-Box 360 pad",
    # Bus type,      vendor, product, version
    ecodes.BUS_USB,  1118,   654,     272,
    # Axes
    {
        "ABS_X":  "left_analog_x",
        "ABS_Y":  "left_analog_y",
        "ABS_RX": "right_analog_x",
        "ABS_RY": "right_analog_y",
        "ABS_Z":  "l2_analog",
        "ABS_RZ": "r2_analog"
    },
    # Axes settings
    {},
    #Buttons
    {
        "BTN_START":  "button_options",
        "BTN_MODE":   "button_ps",
        "BTN_SELECT": "button_share",
        "BTN_A":      "button_cross",
        "BTN_B":      "button_circle",
        "BTN_X":      "button_square",
        "BTN_Y":      "button_triangle",
        "BTN_TL":     "button_l1",
        "BTN_TR":     "button_r1",
        "BTN_THUMBL": "button_l3",
        "BTN_THUMBR": "button_r3"
    },
    # Hats
    {
        "ABS_HAT0X": ("dpad_left", "dpad_right"),
        "ABS_HAT0Y": ("dpad_up", "dpad_down")
    }
)

create_mapping(
    "xpad_wireless", "Xbox 360 Wireless Receiver",
    # Bus type,      vendor, product, version
    ecodes.BUS_USB,  1118,   1817,    256,
    # Axes
    {
        "ABS_X":  "left_analog_x",
        "ABS_Y":  "left_analog_y",
        "ABS_RX": "right_analog_x",
        "ABS_RY": "right_analog_y",
        "ABS_Z":  "l2_analog",
        "ABS_RZ": "r2_analog"
    },
    # Axes settings
    {},
    #Buttons
    {
        "BTN_START":  "button_options",
        "BTN_MODE":   "button_ps",
        "BTN_SELECT": "button_share",
        "BTN_A":      "button_cross",
        "BTN_B":      "button_circle",
        "BTN_X":      "button_square",
        "BTN_Y":      "button_triangle",
        "BTN_TL":     "button_l1",
        "BTN_TR":     "button_r1",
        "BTN_THUMBL": "button_l3",
        "BTN_THUMBR": "button_r3",

        "BTN_TRIGGER_HAPPY1": "dpad_left",
        "BTN_TRIGGER_HAPPY2": "dpad_right",
        "BTN_TRIGGER_HAPPY3": "dpad_up",
        "BTN_TRIGGER_HAPPY4": "dpad_down",
    },
)

create_mapping(
    "mouse", "DualShock4 Mouse Emulation",
    buttons={
        "BTN_LEFT": "button_trackpad",
    },
    mouse={
        "REL_X": "trackpad_touch0_x",
        "REL_Y": "trackpad_touch0_y"
    },
)

class UInputDevice(object):
    def __init__(self, layout):
        self.joystick_dev = None
        self.evdev_dev = None
        self.ignored_buttons = set()
        self.create_device(layout)

        self._write_cache = {}
        self._scroll_details = {}
        self.emit_reset()

    def create_device(self, layout):
        """Creates a uinput device using the specified layout."""
        events = {ecodes.EV_ABS: [], ecodes.EV_KEY: [],
                  ecodes.EV_REL: []}

        # Joystick device
        if layout.axes or layout.buttons or layout.hats:
            self.joystick_dev = next_joystick_device()

        for name in layout.axes:
            params = layout.axes_options.get(name, DEFAULT_AXIS_OPTIONS)
            if not absInfoUsesValue:
                params = params[1:]
            events[ecodes.EV_ABS].append((name, params))

        for name in layout.hats:
            params = (0, -1, 1, 0, 0)
            if not absInfoUsesValue:
                params = params[1:]
            events[ecodes.EV_ABS].append((name, params))

        for name in layout.buttons:
            events[ecodes.EV_KEY].append(name)

        if layout.mouse:
            self.mouse_pos = {}
            self.mouse_rel = {}
            self.mouse_analog_sensitivity = float(
                layout.mouse_options.get("MOUSE_SENSITIVITY",
                                         DEFAULT_MOUSE_SENSITIVTY)
            )
            self.mouse_analog_deadzone = int(
                layout.mouse_options.get("MOUSE_DEADZONE",
                                         DEFAULT_MOUSE_DEADZONE)
            )
            self.scroll_repeat_delay = float(
                layout.mouse_options.get("MOUSE_SCROLL_REPEAT_DELAY",
                                         DEFAULT_SCROLL_REPEAT_DELAY)
            )
            self.scroll_delay = float(
                layout.mouse_options.get("MOUSE_SCROLL_DELAY",
                                         DEFAULT_SCROLL_DELAY)
            )

            for name in layout.mouse:
                if name in (ecodes.REL_WHEELUP, ecodes.REL_WHEELDOWN):
                    if ecodes.REL_WHEEL not in events[ecodes.EV_REL]:
                        # This ensures that scroll wheel events can work
                        events[ecodes.EV_REL].append(ecodes.REL_WHEEL)
                else:
                    events[ecodes.EV_REL].append(name)
                self.mouse_rel[name] = 0.0

        self.device = UInput(name=layout.name, events=events,
                             bustype=layout.bustype, vendor=layout.vendor,
                             product=layout.product, version=layout.version)
        self.layout = layout

    def write_event(self, etype, code, value):
        """Writes a event to the device, if it has changed."""
        last_value = self._write_cache.get(code)
        if last_value != value:
            self.device.write(etype, code, value)
            self._write_cache[code] = value

    def emit(self, report):
        """Writes axes, buttons and hats with values from the report to
        the device."""
        for name, attr in self.layout.axes.items():
            value = getattr(report, attr)
            self.write_event(ecodes.EV_ABS, name, value)

        for name, attr in self.layout.buttons.items():
            attr, modifier = attr

            if attr in self.ignored_buttons:
                value = False
            else:
                value = getattr(report, attr)

            if modifier and "analog" in attr:
                if modifier == "+":
                    value = value > (128 + DEFAULT_A2D_DEADZONE)
                elif modifier == "-":
                    value = value < (128 - DEFAULT_A2D_DEADZONE)

            self.write_event(ecodes.EV_KEY, name, value)

        for name, attr in self.layout.hats.items():
            if getattr(report, attr[0]):
                value = -1
            elif getattr(report, attr[1]):
                value = 1
            else:
                value = 0

            self.write_event(ecodes.EV_ABS, name, value)

        self.device.syn()

    def emit_reset(self):
        """Resets the device to a blank state."""
        for name in self.layout.axes:
            params = self.layout.axes_options.get(name, DEFAULT_AXIS_OPTIONS)
            self.write_event(ecodes.EV_ABS, name, int(sum(params[1:3]) / 2))

        for name in self.layout.buttons:
            self.write_event(ecodes.EV_KEY, name, False)

        for name in self.layout.hats:
            self.write_event(ecodes.EV_ABS, name, 0)

        self.device.syn()

    def emit_mouse(self, report):
        """Calculates relative mouse values from a report and writes them."""
        for name, attr in self.layout.mouse.items():
            # If the attr is a tuple like (left_analog_y, "-")
            # then set the attr to just be the first item
            attr, modifier = attr

            if attr.startswith("trackpad_touch"):
                active_attr = attr[:16] + "active"
                if not getattr(report, active_attr):
                    self.mouse_pos.pop(name, None)
                    continue

                pos = getattr(report, attr)
                if name not in self.mouse_pos:
                    self.mouse_pos[name] = pos

                sensitivity = 0.5
                self.mouse_rel[name] += (pos - self.mouse_pos[name]) * sensitivity
                self.mouse_pos[name] = pos

            elif "analog" in attr:
                pos = getattr(report, attr)
                if (pos > (128 + self.mouse_analog_deadzone)
                    or pos < (128 - self.mouse_analog_deadzone)):
                    accel = (pos - 128) / 10
                else:
                    continue

                # If a minus modifier has been given then minus the acceleration
                # to invert the direction.
                if (modifier and modifier == "-"):
                    accel = -accel

                sensitivity = self.mouse_analog_sensitivity
                self.mouse_rel[name] += accel * sensitivity

            # Emulate mouse wheel (needs special handling)
            if name in (ecodes.REL_WHEELUP, ecodes.REL_WHEELDOWN):
                ecode = ecodes.REL_WHEEL # The real event we need to emit
                write = False
                if getattr(report, attr):
                    self._scroll_details['direction'] = name
                    now = time.time()
                    last_write = self._scroll_details.get('last_write')
                    if not last_write:
                        # No delay for the first button press for fast feedback
                        write = True
                        self._scroll_details['count'] = 0
                    if name == ecodes.REL_WHEELUP:
                        value = 1
                    elif name == ecodes.REL_WHEELDOWN:
                        value = -1
                    if last_write:
                        # Delay at least one cycle before continual scrolling
                        if self._scroll_details['count'] > 1:
                            if now - last_write > self.scroll_delay:
                                write = True
                        elif now - last_write > self.scroll_repeat_delay:
                            write = True
                    if write:
                        self.device.write(ecodes.EV_REL, ecode, value)
                        self._scroll_details['last_write'] = now
                        self._scroll_details['count'] += 1
                        continue # No need to proceed further
                else:
                    # Reset so you can quickly tap the button to scroll
                    if self._scroll_details.get('direction') == name:
                        self._scroll_details['last_write'] = 0
                        self._scroll_details['count'] = 0

            rel = int(self.mouse_rel[name])
            self.mouse_rel[name] = self.mouse_rel[name] - rel
            self.device.write(ecodes.EV_REL, name, rel)

        self.device.syn()

def create_uinput_device(mapping):
    """Creates a uinput device."""
    if mapping not in _mappings:
        raise DeviceError("Unknown device mapping: {0}".format(mapping))

    try:
        mapping = _mappings[mapping]
        device = UInputDevice(mapping)
    except UInputError as err:
        raise DeviceError(err)

    return device

def parse_uinput_mapping(name, mapping):
    """Parses a dict of mapping options."""
    axes, buttons, mouse, mouse_options = {}, {}, {}, {}
    description = "ds4drv custom mapping ({0})".format(name)

    for key, attr in mapping.items():
        key = key.upper()
        if key.startswith("BTN_") or key.startswith("KEY_"):
            buttons[key] = attr
        elif key.startswith("ABS_"):
            axes[key] = attr
        elif key.startswith("REL_"):
            mouse[key] = attr
        elif key.startswith("MOUSE_"):
            mouse_options[key] = attr

    create_mapping(name, description, axes=axes, buttons=buttons,
                   mouse=mouse, mouse_options=mouse_options)

def next_joystick_device():
    """Finds the next available js device name."""
    for i in range(100):
        dev = "/dev/input/js{0}".format(i)
        if not os.path.exists(dev):
            return dev


text:
package com.blockwithme.tactors;

import org.agilewiki.jactor.api.Actor;
import org.agilewiki.jactor.util.Ancestor;
import org.agilewiki.jactor.util.Named;

import com.blockwithme.time.TimeListener;
import com.blockwithme.time.Timed;
import com.blockwithme.util.IDedAndNamed;

/**
 * Base interface for all temporal actors.
 *
 * TODO Now that all TActor are weakly referenced, we could add lifecycles to them.
 *
 * @author monster
 */
public interface TActor extends Named, Actor, Ancestor, IDedAndNamed, Timed,
        TimeListener {
    /** @see org.agilewiki.jactor.api.Actor#getMailbox() */
    @Override
    public TMailbox getMailbox();

    /**
     * Returns the Actor ID. It will be within [Long.MIN_VALUE,Long.MAX_VALUE], but not 0.
     * It is derived from the Mailbox ID.
     */
    @Override
    long id();

    /** Returns the actor name. If defined, it will be unique within the mailbox. */
    @Override
    String name();

    /** The parent of a TActor is always another TActor, or null. */
    @Override
    TActor getParent();

    /**
     * Creates a copy of this actor, with the given new Mailbox.
     * If the mailbox is null, the current mailbox is used.
     */
    TActor copy(final TMailbox mailbox);

    /** Returns true, when this actor has the same Mailbox as the actor passed as parameter */
    boolean sameMailbox(final Actor other);
}


text:
import { FETCH_STORY_REDDIT_ATTENTION } from '../../actions/storyActions';
import { createAsyncReducer } from '../../lib/reduxHelpers';

const reddit = createAsyncReducer({
  initialState: {
    bySub: [],
    total: 0,
  },
  action: FETCH_STORY_REDDIT_ATTENTION,
});

export default reddit;


text:
var deepExtend = require("deep-extend"),
    baseModelMixin = require("../../model/baseModelMixin");

module.exports = function(attributes, options) {

    //mix attributes which we want to be present in each entity
    deepExtend(attributes, baseModelMixin.fields);

    //mix options which we want to be present in each entity
    deepExtend(options, baseModelMixin.options);
};