Add SAMBRO and helper scripts

This commit is contained in:
Disassembler 2022-03-07 12:41:09 +01:00
parent ee7b5fddc7
commit da3200c8c7
No known key found for this signature in database
GPG Key ID: 524BD33A0EE29499
8 changed files with 12918 additions and 1 deletions

2
.env
View File

@ -8,7 +8,7 @@ SAHANA_ADMIN_USER=admin
SAHANA_ADMIN_PASSWORD=sahana
SAHANA_MAIL_HOST=127.0.0.1:25
SAHANA_MAIL_SENDER="'Sahana' <sahana@example.org>"
SAHANA_MAIL_SENDER='Sahana' <sahana@example.org>
SAHANA_MAIL_APPROVER=useradmin@example.org
SAHANA_API_KEY_GOOGLE=

View File

@ -0,0 +1,95 @@
#!/usr/bin/env python3
import argparse
import csv
import io
import os
from ast import literal_eval
from pprint import pprint
# GIS CSV can have extra large fields
csv.field_size_limit(2147483647)
# Strings which don't exist verbatim in the code
extras = (
'Enter a number between %(min)g and %(max)g',
'Enter a number greater than or equal to %(min)g',
'Enter a number less than or equal to %(max)g',
'Enter an integer between %(min)g and %(max)g',
'Enter an integer greater than or equal to %(min)g',
'Enter an integer less than or equal to %(max)g',
)
def get_file_contents(filename):
# Return decoded file contents
with open(filename, 'rb') as f:
file_contents = f.read()
try:
return file_contents.decode('utf-8')
except UnicodeDecodeError:
return file_contents.decode('latin-1')
def get_csv_contents(filename):
# Return list of all fields from a CSV file
f = io.StringIO(get_file_contents(filename))
csv_contents = []
for row in csv.reader(f):
csv_contents.extend(row)
return csv_contents
def main(args):
basename = os.path.basename(args.langfile)
# Load existing translations from the current (old) Sahana Eden instance
with open(os.path.join(args.web2pydir, 'applications/eden/languages', basename)) as f:
old_translations = literal_eval(f.read())
# Load translations produced by sahana-lang-convert.py
with open(args.langfile) as f:
translations = literal_eval(f.read())
missing_translations = {key:value for key,value in old_translations.items() if key not in translations}
for root, dirs, files in os.walk(args.web2pydir):
# Iterate over all web2py subdirectories except "languages" which already contain translations
if 'languages' in dirs:
dirs.remove('languages')
for file in files:
extension = os.path.splitext(file)[1].lower()
filename = os.path.join(root, file)
if extension in ('.py', '.html', '.js'):
try:
file_contents = get_file_contents(filename)
except UnicodeDecodeError:
continue
for key,value in missing_translations.copy().items():
# Naively search for quoted strings in .py .html and .js files
if f"'{key}'" in file_contents or f'"{key}"' in file_contents:
translations[key] = value
del missing_translations[key]
elif extension == '.csv':
try:
csv_contents = get_csv_contents(filename)
except UnicodeDecodeError:
continue
for key,value in missing_translations.copy().items():
# Naively search for full strings in csv fields
if key in csv_contents:
translations[key] = value
del missing_translations[key]
for key in extras:
# Add the extra translations which are never matched verbatim
if key not in translations:
translations[key] = old_translations[key]
with open(basename, 'w') as langfile:
# Write the updated translation file
print('# -*- coding: utf-8 -*-', file=langfile)
pprint(translations, langfile, 0, 8192)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Spotter Cluster Sahana Eden translation comparator')
parser.add_argument('langfile', help='New translation file (with possibly missing strings).')
parser.add_argument('web2pydir', help='Path to Web2py root directory.')
main(parser.parse_args())

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
import argparse
import csv
import os
import xlrd
from pprint import pprint
def main(args):
translations = {}
basename, extension = os.path.splitext(args.inputfile)
extension = extension.lower()
if extension == '.csv':
# Read CSV file, assume the same structure as given by the export
with open(args.inputfile, 'r') as csvfile:
reader = csv.reader(csvfile)
for line in reader:
translations[line[1]] = line[2]
elif extension in ('.xls', '.xlsx'):
# Read the XLS(X) file, assume type of columns from their count
sheet = xlrd.open_workbook(args.inputfile).sheet_by_index(0)
source_col,target_col = (1,2) if sheet.row(0)[2] else (0,1)
for i in range(1, sheet.nrows):
row = sheet.row(i)
translations[row[source_col].value] = row[target_col].value
else:
print('Unknown input file extension')
return
with open('{}.py'.format(basename), 'w') as langfile:
# Write the translation file
print('# -*- coding: utf-8 -*-', file=langfile)
pprint(translations, langfile, 0, 8192)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Spotter Cluster Sahana Eden translation converter')
parser.add_argument('inputfile', help='CSV or XLS translation file.')
main(parser.parse_args())

11
sahana-sambro/Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM sahana:latest
COPY image.d /
RUN \
# Change ownership of the newly copied files
find /srv/web2py ! -user sahana -exec chown -R sahana:sahana {} +
ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 8080
VOLUME ["/srv/web2py/applications/eden/models", "/srv/web2py/applications/eden/databases", "/srv/web2py/applications/eden/errors", "/srv/web2py/applications/eden/sessions", "/srv/web2py/applications/eden/uploads"]

View File

@ -0,0 +1,250 @@
# -*- coding: utf-8 -*-
"""
Machine-specific settings
All settings which are typically edited for a specific machine should be done here
Deployers should ideally not need to edit any other files outside of their template folder
Note for Developers:
/models/000_config.py is NOT in the Git repository, to avoid leaking of
sensitive or irrelevant information into the repository.
For changes to be committed, please also edit:
modules/templates/000_config.py
"""
import os
# Remove this line when you have edited this file sufficiently to proceed to the web interface
FINISHED_EDITING_CONFIG_FILE = True
# Select the Template
# - which Modules are enabled
# - PrePopulate data
# - Security Policy
# - Workflows
# - Theme
# - note that you should restart your web2py after changing this setting
settings.base.template = "SAMBRO"
# Database settings
# Uncomment to use a different database, other than sqlite
settings.database.db_type = "postgres"
#settings.database.db_type = "mysql"
# Uncomment to use a different host
settings.database.host = os.getenv("POSTGRES_HOST", "localhost")
# Uncomment to use a different port
#settings.database.port = 3306
#settings.database.port = 5432
# Uncomment to select a different name for your database
settings.database.database = os.getenv("POSTGRES_DB", "sahana")
# Uncomment to select a different username for your database
settings.database.username = os.getenv("POSTGRES_USER", "sahana")
# Uncomment to set the password
# NB Web2Py doesn't like passwords with an @ in them
settings.database.password = os.getenv("POSTGRES_PASSWORD", "password")
# Uncomment to use a different pool size
#settings.database.pool_size = 30
# Do we have a spatial DB available? (currently supports PostGIS. Spatialite to come.)
settings.gis.spatialdb = True
# Base settings
settings.base.system_name = T("Sahana Alerting and Messaging Broker")
settings.base.system_name_short = T("SAMBRO")
# Set this to the Public URL of the instance
settings.base.public_url = os.getenv("SAHANA_PUBLIC_URL", "http://127.0.0.1:8080")
# Switch to "False" in Production for a Performance gain
# (need to set to "True" again when Table definitions are changed)
settings.base.migrate = True
# To just create the .table files (also requires migrate=True):
#settings.base.fake_migrate = True
# Set this to True to switch to Debug mode
# Debug mode means that uncompressed CSS/JS files are loaded
# JS Debug messages are also available in the Console
# can also load an individual page in debug mode by appending URL with
# ?debug=1
settings.base.debug = True
# Uncomment this to prevent automated test runs from remote
# settings.base.allow_testing = False
# Configure the log level ("DEBUG", "INFO", "WARNING", "ERROR" or "CRITICAL"), None = turn off logging (default)
#settings.log.level = "ERROR" # DEBUG set automatically when base.debug is True
# Uncomment to prevent writing log messages to the console (sys.stderr)
#settings.log.console = False
# Configure a log file (file name)
#settings.log.logfile = None
# Uncomment to get detailed caller information
#settings.log.caller_info = True
# Uncomment to use Content Delivery Networks to speed up Internet-facing sites
#settings.base.cdn = True
# Allow language files to be updated automatically
#settings.L10n.languages_readonly = False
# This setting should be changed _before_ registering the 1st user
# - should happen automatically if installing using supported scripts
settings.auth.hmac_key = os.getenv("SAHANA_HMAC_KEY", "akeytochange")
# If using Masterkey Authentication, then set this to a deployment-specific 32 char string:
#settings.auth.masterkey_app_key = "randomstringrandomstringrandomstring"
# Minimum Password Length
#settings.auth.password_min_length = 8
# Email settings
# Outbound server
settings.mail.server = os.getenv("SAHANA_MAIL_HOST", "127.0.0.1:25")
#settings.mail.tls = True
# Useful for Windows Laptops:
# https://www.google.com/settings/security/lesssecureapps
#settings.mail.server = "smtp.gmail.com:587"
#settings.mail.tls = True
#settings.mail.login = "username:password"
# From Address - until this is set, no mails can be sent
settings.mail.sender = os.getenv("SAHANA_MAIL_SENDER", "'Sahana' <sahana@example.org>")
# Default email address to which requests to approve new user accounts gets sent
# This can be overridden for specific domains/organisations via the auth_domain table
settings.mail.approver = os.getenv("SAHANA_MAIL_APPROVER", "useradmin@example.org")
# Daily Limit on Sending of emails
#settings.mail.limit = 1000
# Uncomment to restrict to specific country/countries
#settings.gis.countries= ("LK",)
# Bing API Key (for Map layers)
# http://www.microsoft.com/maps/create-a-bing-maps-key.aspx
#settings.gis.api_bing = ""
# GetAddress API Key (for GetAddress.io Postcode to Address lookup)
#settings.gis.api_getaddress = ""
# Google API Key (for Google Maps Layers)
settings.gis.api_google = os.getenv("SAHANA_API_KEY_GOOGLE", "")
# OpenWeatherMap API Key (for OpenWeatherMap Layers)
settings.gis.api_openweathermap = os.getenv("SAHANA_API_KEY_OPENWEATHERMAP", "")
# GeoNames username
#settings.gis.geonames_username = ""
# Fill this in to get a Facebook Pixel for your site
#settings.base.facebook_pixel_id = ""
# Fill this in to get Google Analytics for your site
#settings.base.google_analytics_tracking_id = ""
# Chat server, see: http://eden.sahanafoundation.org/wiki/InstallationGuidelines/Chat
#settings.base.chat_server = {
# "ip": "127.0.0.1",
# "port": 7070,
# "name": "servername",
# # Default group everyone is added to
# "groupname" : "everyone",
# "server_db" : "openfire",
# # These settings fallback to main DB settings if not specified
# # Only mysql/postgres supported
# #"server_db_type" : "mysql",
# #"server_db_username" : "",
# #"server_db_password": "",
# #"server_db_port" : 3306,
# #"server_db_ip" : "127.0.0.1",
# }
# GeoServer (Currently used by GeoExplorer. Will allow REST control of GeoServer.)
# NB Needs to be publically-accessible URL for querying via client JS
#settings.gis.geoserver_url = "http://localhost/geoserver"
#settings.gis.geoserver_username = "admin"
#settings.gis.geoserver_password = ""
# Print Service URL: http://eden.sahanafoundation.org/wiki/BluePrintGISPrinting
#settings.gis.print_service = "/geoserver/pdf/"
# Google OAuth (to allow users to login using Google)
# https://code.google.com/apis/console/
#settings.auth.google_id = ""
#settings.auth.google_secret = ""
# Pootle server
# settings.L10n.pootle_url = "http://pootle.sahanafoundation.org/"
# settings.L10n.pootle_username = "username"
# settings.L10n.pootle_password = "*****"
# SOLR server for Full-Text Search
#settings.base.solr_url = "http://127.0.0.1:8983/solr/"
# Memcache server to allow sharing of sessions across instances
#settings.base.session_memcache = '127.0.0.1:11211'
settings.base.session_db = True
# UI options
# Should user be prompted to save before navigating away?
#settings.ui.navigate_away_confirm = False
# Should user be prompted to confirm actions?
#settings.ui.confirm = False
# Should potentially large dropdowns be turned into autocompletes?
# (unused currently)
#settings.ui.autocomplete = True
#settings.ui.read_label = "Details"
#settings.ui.update_label = "Edit"
# Audit settings
# - can be a callable for custom hooks (return True to also perform normal logging, or False otherwise)
# NB Auditing (especially Reads) slows system down & consumes diskspace
#settings.security.audit_write = False
#settings.security.audit_read = False
# Performance Options
# Maximum number of search results for an Autocomplete Widget
#settings.search.max_results = 200
# Maximum number of features for a Map Layer
#settings.gis.max_features = 1000
# CAP Settings
# Change for different authority and organisations
# See http://alerting.worldweather.org/ for oid
# Country root oid. The oid for the organisation includes this base
#settings.cap.identifier_oid = "2.49.0.0.608.0"
# Set the period (in days) after which alert info segments expire (default=2)
#settings.cap.info_effective_period = 2
# =============================================================================
# Import the settings from the Template
# - note: invalid settings are ignored
#
settings.import_template()
# =============================================================================
# Over-rides to the Template may be done here
#
# e.g.
#settings.security.self_registration = True
#settings.base.system_name = T("Sahana TEST")
#settings.base.prepopulate = ("MY_TEMPLATE_ONLY")
#settings.base.prepopulate += ("default", "default/users")
#settings.base.theme = "default"
settings.L10n.default_language = "cs"
#settings.security.policy = 7 # Organisation-ACLs
# Enable Additional Module(s)
#from gluon.storage import Storage
#settings.modules["delphi"] = Storage(
# name_nice = T("Delphi Decision Maker"),
# restricted = False,
# module_type = 10,
# )
# Disable a module which is normally used by the template
# - NB Only templates with adaptive menus will work nicely with this!
#del settings.modules["irs"]
# Production instances should set this before prepopulate is run
#settings.base.prepopulate_demo = 0
# After 1st_run, set this for Production to save 1x DAL hit/request
#settings.base.prepopulate = 0
# =============================================================================
# A version number to tell update_check if there is a need to refresh the
# running copy of this file
VERSION = 1
# END =========================================================================

View File

@ -0,0 +1,309 @@
# -*- coding: utf-8 -*-
"""
Global settings:
Those which are typically edited during a deployment are in
000_config.py & their results parsed into here. Deployers
shouldn't typically need to edit any settings here.
"""
# Keep all our configuration options off the main global variables
# Use response.s3 for one-off variables which are visible in views without explicit passing
s3.formats = Storage()
# Workaround for this Bug in Selenium with FF4:
# http://code.google.com/p/selenium/issues/detail?id=1604
s3.interactive = settings.get_ui_confirm()
s3.base_url = "%s/%s" % (settings.get_base_public_url(),
appname,
)
s3.download_url = "%s/default/download" % s3.base_url
# -----------------------------------------------------------------------------
# Client tests
# Check whether browser is Mobile & store result in session
# - commented-out until we make use of it
#if session.s3.mobile is None:
# from s3 import s3_is_mobile_client
# session.s3.mobile = s3_is_mobile_client(request)
#if session.s3.browser is None:
# from s3 import s3_populate_browser_compatibility
# session.s3.browser = s3_populate_browser_compatibility(request)
# -----------------------------------------------------------------------------
# Global variables
# Strings to i18n
# Common Labels
#messages["BREADCRUMB"] = ">> "
messages["UNKNOWN_OPT"] = ""
messages["NONE"] = ""
messages["OBSOLETE"] = "Obsolete"
messages["READ"] = settings.get_ui_label_read()
messages["UPDATE"] = settings.get_ui_label_update()
messages["DELETE"] = "Delete"
messages["COPY"] = "Copy"
messages["NOT_APPLICABLE"] = "N/A"
messages["ADD_PERSON"] = "Create a Person"
messages["ADD_LOCATION"] = "Create Location"
messages["SELECT_LOCATION"] = "Select a location"
messages["COUNTRY"] = "Country"
messages["ORGANISATION"] = "Organization"
messages["AUTOCOMPLETE_HELP"] = "Enter some characters to bring up a list of possible matches"
for u in messages:
if isinstance(messages[u], str):
globals()[u] = T(messages[u])
# CRUD Labels
s3.crud_labels = Storage(READ = READ,
UPDATE = UPDATE,
DELETE = DELETE,
COPY = COPY,
NONE = NONE,
)
# Error Messages
ERROR["BAD_RECORD"] = "Record not found!"
ERROR["BAD_METHOD"] = "Unsupported method!"
ERROR["BAD_FORMAT"] = "Unsupported data format!"
ERROR["BAD_REQUEST"] = "Invalid request"
ERROR["BAD_SOURCE"] = "Invalid source"
ERROR["BAD_TEMPLATE"] = "XSLT stylesheet not found"
ERROR["BAD_RESOURCE"] = "Nonexistent or invalid resource"
ERROR["DATA_IMPORT_ERROR"] = "Data import error"
ERROR["INTEGRITY_ERROR"] = "Integrity error: record can not be deleted while it is referenced by other records"
ERROR["METHOD_DISABLED"] = "Method disabled"
ERROR["NO_MATCH"] = "No matching element found in the data source"
ERROR["NOT_IMPLEMENTED"] = "Not implemented"
ERROR["NOT_PERMITTED"] = "Operation not permitted"
ERROR["PARSE_ERROR"] = "XML parse error"
ERROR["TRANSFORMATION_ERROR"] = "XSLT transformation error"
ERROR["UNAUTHORISED"] = "Not Authorized"
ERROR["VALIDATION_ERROR"] = "Validation error"
# To get included in <HEAD>
s3.stylesheets = []
s3.external_stylesheets = []
# To get included at the end of <BODY>
s3.scripts = []
s3.scripts_modules = []
s3.js_global = []
s3.jquery_ready = []
# -----------------------------------------------------------------------------
# Languages
s3.l10n_languages = settings.get_L10n_languages()
# Default strings are in US English
T.current_languages = ("en", "en-us")
# Check if user has selected a specific language
if get_vars._language:
language = get_vars._language
session.s3.language = language
elif session.s3.language:
# Use the last-selected language
language = session.s3.language
elif auth.is_logged_in():
# Use user preference
language = auth.user.language
else:
# Use system default
language = settings.get_L10n_default_language()
#else:
# # Use what browser requests (default web2py behaviour)
# T.force(T.http_accept_language)
# IE doesn't set request.env.http_accept_language
#if language != "en":
T.force(language)
# Store for views (e.g. Ext)
if language.find("-") == -1:
# Ext peculiarities
if language == "vi":
s3.language = "vn"
elif language == "el":
s3.language = "el_GR"
else:
s3.language = language
else:
lang_parts = language.split("-")
s3.language = "%s_%s" % (lang_parts[0], lang_parts[1].upper())
# List of Languages which use a Right-to-Left script (Arabic, Hebrew, Farsi, Urdu)
if language in ("ar", "prs", "ps", "ur"):
s3.rtl = True
else:
s3.rtl = False
# -----------------------------------------------------------------------------
# Auth
_settings = auth.settings
_settings.lock_keys = False
_settings.expiration = 28800 # seconds
if settings.get_auth_openid():
# Requires http://pypi.python.org/pypi/python-openid/
try:
from gluon.contrib.login_methods.openid_auth import OpenIDAuth
openid_login_form = OpenIDAuth(auth)
from gluon.contrib.login_methods.extended_login_form import ExtendedLoginForm
_settings.login_form = ExtendedLoginForm(auth, openid_login_form,
signals = ["oid", "janrain_nonce"],
)
except ImportError:
session.warning = "Library support not available for OpenID"
# Allow use of LDAP accounts for login
# NB Currently this means that change password should be disabled:
#_settings.actions_disabled.append("change_password")
# (NB These are not automatically added to PR or to Authenticated role since they enter via the login() method not register())
#from gluon.contrib.login_methods.ldap_auth import ldap_auth
# Require even alternate login methods to register users 1st
#_settings.alternate_requires_registration = True
# Active Directory
#_settings.login_methods.append(ldap_auth(mode="ad", server="dc.domain.org", base_dn="ou=Users,dc=domain,dc=org"))
# or if not wanting local users at all (no passwords saved within DB):
#_settings.login_methods = [ldap_auth(mode="ad", server="dc.domain.org", base_dn="ou=Users,dc=domain,dc=org")]
# Domino
#_settings.login_methods.append(ldap_auth(mode="domino", server="domino.domain.org"))
# OpenLDAP
#_settings.login_methods.append(ldap_auth(server="directory.sahanafoundation.org", base_dn="ou=users,dc=sahanafoundation,dc=org"))
# Allow use of Email accounts for login
#_settings.login_methods.append(email_auth("smtp.gmail.com:587", "@gmail.com"))
# Require captcha verification for registration
#auth.settings.captcha = RECAPTCHA(request, public_key="PUBLIC_KEY", private_key="PRIVATE_KEY")
# Require Email Verification
_settings.registration_requires_verification = settings.get_auth_registration_requires_verification()
_settings.on_failed_authorization = URL(c="default", f="user",
args = "not_authorized",
)
_settings.reset_password_requires_verification = True
_settings.verify_email_next = URL(c="default", f="index")
# Require Admin approval for self-registered users
_settings.registration_requires_approval = settings.get_auth_registration_requires_approval()
# We don't wish to clutter the groups list with 1 per user.
_settings.create_user_groups = False
# We need to allow basic logins for Webservices
_settings.allow_basic_login = True
_settings.logout_onlogout = s3_auth_on_logout
_settings.login_onaccept = s3_auth_on_login
# Now read in auth.login() to avoid setting unneccesarily in every request
#_settings.login_next = settings.get_auth_login_next()
if settings.has_module("vol") and \
settings.get_auth_registration_volunteer():
_settings.register_next = URL(c="vol", f="person")
# Languages available in User Profiles
#if len(s3.l10n_languages) > 1:
# from s3 import IS_ISO639_2_LANGUAGE_CODE
# _settings.table_user.language.requires = IS_ISO639_2_LANGUAGE_CODE(sort = True,
# translate = True,
# zero = None,
# )
#else:
# field = _settings.table_user.language
# field.default = s3.l10n_languages.keys()[0]
# field.readable = False
# field.writable = False
_settings.lock_keys = True
# -----------------------------------------------------------------------------
# Mail
# These settings could be made configurable as part of the Messaging Module
# - however also need to be used by Auth (order issues)
sender = settings.get_mail_sender()
if sender:
mail.settings.sender = sender
mail.settings.server = settings.get_mail_server()
mail.settings.tls = settings.get_mail_server_tls()
mail_server_login = settings.get_mail_server_login()
if mail_server_login:
mail.settings.login = mail_server_login
# Email settings for registration verification and approval
_settings.mailer = mail
# -----------------------------------------------------------------------------
# Session
# Custom Notifications
response.error = session.error
response.confirmation = session.confirmation
response.information = session.information
response.warning = session.warning
session.error = []
session.confirmation = []
session.information = []
session.warning = []
# Shortcuts for system role IDs, see modules/s3aaa.py/AuthS3
#system_roles = auth.get_system_roles()
#ADMIN = system_roles.ADMIN
#AUTHENTICATED = system_roles.AUTHENTICATED
#ANONYMOUS = system_roles.ANONYMOUS
#EDITOR = system_roles.EDITOR
#MAP_ADMIN = system_roles.MAP_ADMIN
#ORG_ADMIN = system_roles.ORG_ADMIN
#ORG_GROUP_ADMIN = system_roles.ORG_GROUP_ADMIN
# -----------------------------------------------------------------------------
# CRUD
s3_formstyle = settings.get_ui_formstyle()
s3_formstyle_read = settings.get_ui_formstyle_read()
s3_formstyle_mobile = s3_formstyle
submit_button = T("Save")
s3_crud = s3.crud
s3_crud.formstyle = s3_formstyle
s3_crud.formstyle_read = s3_formstyle_read
s3_crud.submit_button = submit_button
# Optional class for Submit buttons
#s3_crud.submit_style = "submit-button"
s3_crud.confirm_delete = T("Do you really want to delete these records?")
s3_crud.archive_not_delete = settings.get_security_archive_not_delete()
s3_crud.navigate_away_confirm = settings.get_ui_navigate_away_confirm()
# JSON Formats
s3.json_formats = ("geojson", "s3json")
# CSV Formats
s3.csv_formats = ("hrf", "s3csv")
# Datatables default number of rows per page
s3.ROWSPERPAGE = 20
# Valid Extensions for Image Upload fields
s3.IMAGE_EXTENSIONS = ("png", "PNG", "jpg", "JPG", "jpeg", "JPEG")
# Default CRUD strings
s3.crud_strings = Storage(label_create = T("Add Record"),
title_display = T("Record Details"),
title_list = T("Records"),
title_update = T("Edit Record"),
title_map = T("Map"),
title_report = T("Report"),
label_list_button = T("List Records"),
label_delete_button = T("Delete Record"),
msg_record_created = T("Record added"),
msg_record_modified = T("Record updated"),
msg_record_deleted = T("Record deleted"),
msg_list_empty = T("No Records currently available"),
msg_match = T("Matching Records"),
msg_no_match = T("No Matching Records"),
)
# END =========================================================================

File diff suppressed because it is too large Load Diff