From cb6689e340cf22de4e061f96c665ed45bcc2709f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Cant=C3=B9?= Date: Fri, 5 Apr 2024 15:45:16 +0200 Subject: [PATCH] initial commit --- .env.sample | 261 + .gitignore | 72 + .override_dev_env.sample | 103 + Dockerfile | 52 + README.md | 396 ++ Vagrantfile.stack | 58 + create-envfile.py | 208 + dev_config.yml | 8 + docker-build.sh | 1 + docker-compose.yml | 200 + docker-purge.sh | 2 + docker/geoserver/Dockerfile | 2 + docker/geoserver_data/Dockerfile | 2 + docker/letsencrypt/Dockerfile | 2 + docker/nginx/Dockerfile | 2 + docker/postgresql/Dockerfile | 2 + generated-project-vagrant-compose.sh | 22 + generated-project-vagrant-swarm.sh | 22 + geonode-stack.yml | 165 + playbook.yml | 46 + src/Makefile | 29 + src/README.md | 1 + src/celery-cmd | 22 + src/celery.sh | 4 + src/dev_config.yml | 8 + src/entrypoint.sh | 73 + src/fixtures/apikey_docker.json | 11 + src/fixtures/default_oauth_apps.json | 21 + src/fixtures/default_oauth_apps_docker.json | 21 + src/fixtures/django_celery_beat.json | 72 + src/fixtures/initial_data.json | 4877 +++++++++++++++++ src/fixtures/sample_admin.json | 18 + src/fixtures/sites_template.json | 10 + src/jetty-runner.xml | 10 + src/manage.py | 31 + src/manage.sh | 2 + src/manage_dev.sh.sample | 3 + src/monitoring-cron | 3 + src/package/support/geonode.binary | 15 + src/package/support/geonode.updateip | 213 + src/pavement.py | 1260 +++++ src/paver.sh | 2 + src/paver_dev.sh.sample | 3 + src/requirements.txt | 1 + src/scripts/misc/apache2/geonode.conf.sample | 108 + src/scripts/misc/cleanup_pyc.sh | 9 + src/scripts/misc/jetty-runner.xml | 12 + src/setup.py | 49 + src/tasks.py | 761 +++ src/urban/__init__.py | 32 + src/urban/apps.py | 41 + src/urban/br/backup.sh | 45 + src/urban/br/restore.sh | 97 + src/urban/br/settings_docker.ini | 17 + src/urban/celeryapp.py | 38 + src/urban/locale/.gitkeep | 0 src/urban/settings.py | 164 + src/urban/static/README | 20 + src/urban/static/css/site_base.css | 0 src/urban/static/gulpfile.js | 23 + src/urban/static/img/README | 0 src/urban/static/img/bing_aerial_w_labels.png | Bin 0 -> 19491 bytes src/urban/static/img/bing_canvas_dark.png | Bin 0 -> 9927 bytes src/urban/static/img/bing_road_on_demand.png | Bin 0 -> 12703 bytes src/urban/static/js/README | 0 src/urban/static/less/site_base.less | 0 src/urban/static/package.json | 25 + .../_geonode_config.html | 9 + src/urban/urls.py | 30 + src/urban/version.py | 56 + src/urban/wsgi.py | 49 + src/uwsgi.ini | 47 + src/wait-for-databases.sh | 23 + 73 files changed, 9991 insertions(+) create mode 100644 .env.sample create mode 100644 .gitignore create mode 100644 .override_dev_env.sample create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 Vagrantfile.stack create mode 100644 create-envfile.py create mode 100644 dev_config.yml create mode 100755 docker-build.sh create mode 100644 docker-compose.yml create mode 100755 docker-purge.sh create mode 100644 docker/geoserver/Dockerfile create mode 100644 docker/geoserver_data/Dockerfile create mode 100644 docker/letsencrypt/Dockerfile create mode 100644 docker/nginx/Dockerfile create mode 100644 docker/postgresql/Dockerfile create mode 100644 generated-project-vagrant-compose.sh create mode 100644 generated-project-vagrant-swarm.sh create mode 100644 geonode-stack.yml create mode 100644 playbook.yml create mode 100644 src/Makefile create mode 100644 src/README.md create mode 100755 src/celery-cmd create mode 100644 src/celery.sh create mode 100644 src/dev_config.yml create mode 100755 src/entrypoint.sh create mode 100644 src/fixtures/apikey_docker.json create mode 100644 src/fixtures/default_oauth_apps.json create mode 100644 src/fixtures/default_oauth_apps_docker.json create mode 100644 src/fixtures/django_celery_beat.json create mode 100644 src/fixtures/initial_data.json create mode 100644 src/fixtures/sample_admin.json create mode 100644 src/fixtures/sites_template.json create mode 100644 src/jetty-runner.xml create mode 100755 src/manage.py create mode 100755 src/manage.sh create mode 100644 src/manage_dev.sh.sample create mode 100644 src/monitoring-cron create mode 100644 src/package/support/geonode.binary create mode 100644 src/package/support/geonode.updateip create mode 100644 src/pavement.py create mode 100755 src/paver.sh create mode 100644 src/paver_dev.sh.sample create mode 100644 src/requirements.txt create mode 100644 src/scripts/misc/apache2/geonode.conf.sample create mode 100644 src/scripts/misc/cleanup_pyc.sh create mode 100644 src/scripts/misc/jetty-runner.xml create mode 100644 src/setup.py create mode 100644 src/tasks.py create mode 100644 src/urban/__init__.py create mode 100644 src/urban/apps.py create mode 100755 src/urban/br/backup.sh create mode 100755 src/urban/br/restore.sh create mode 100644 src/urban/br/settings_docker.ini create mode 100644 src/urban/celeryapp.py create mode 100644 src/urban/locale/.gitkeep create mode 100644 src/urban/settings.py create mode 100644 src/urban/static/README create mode 100644 src/urban/static/css/site_base.css create mode 100644 src/urban/static/gulpfile.js create mode 100644 src/urban/static/img/README create mode 100644 src/urban/static/img/bing_aerial_w_labels.png create mode 100644 src/urban/static/img/bing_canvas_dark.png create mode 100644 src/urban/static/img/bing_road_on_demand.png create mode 100644 src/urban/static/js/README create mode 100644 src/urban/static/less/site_base.less create mode 100644 src/urban/static/package.json create mode 100644 src/urban/templates/geonode-mapstore-client/_geonode_config.html create mode 100644 src/urban/urls.py create mode 100644 src/urban/version.py create mode 100644 src/urban/wsgi.py create mode 100644 src/uwsgi.ini create mode 100755 src/wait-for-databases.sh diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..cbaa948 --- /dev/null +++ b/.env.sample @@ -0,0 +1,261 @@ +COMPOSE_PROJECT_NAME=urban +# See https://github.com/containers/podman/issues/13889 +# DOCKER_BUILDKIT=0 +DOCKER_ENV=production +# See https://github.com/geosolutions-it/geonode-generic/issues/28 +# to see why we force API version to 1.24 +DOCKER_API_VERSION="1.24" +BACKUPS_VOLUME_DRIVER=local + +GEONODE_BASE_IMAGE_VERSION=4.2.3 +NGINX_BASE_IMAGE_VERSION=1.25.3-v1 +LETSENCRYPT_BASE_IMAGE_VERSION=2.6.0-v1 +GEOSERVER_BASE_IMAGE_VERSION=2.24.2-v1 +GEOSERVER_DATA_BASE_IMAGE_VERSION=2.24.2-v1 +POSTGRES_BASE_IMAGE_VERSION=15.3-v1 + +C_FORCE_ROOT=1 +FORCE_REINIT=false +INVOKE_LOG_STDOUT=true + +# LANGUAGE_CODE=it-it +# LANGUAGES=(('en-us','English'),('it-it','Italiano')) + +DJANGO_SETTINGS_MODULE=urban.settings +GEONODE_INSTANCE_NAME=geonode + +# ################# +# backend +# ################# +POSTGRES_USER=postgres +POSTGRES_PASSWORD={pgpwd} +GEONODE_DATABASE=urban +GEONODE_DATABASE_USER=urban +GEONODE_DATABASE_PASSWORD={dbpwd} +GEONODE_GEODATABASE=urban_data +GEONODE_GEODATABASE_USER=urban_data +GEONODE_GEODATABASE_PASSWORD={geodbpwd} +GEONODE_DATABASE_SCHEMA=public +GEONODE_GEODATABASE_SCHEMA=public +DATABASE_HOST=db +DATABASE_PORT=5432 +DATABASE_URL=postgis://urban:{dbpwd}@db:5432/urban +GEODATABASE_URL=postgis://urban_data:{geodbpwd}@db:5432/urban_data +GEONODE_DB_CONN_MAX_AGE=0 +GEONODE_DB_CONN_TOUT=5 +DEFAULT_BACKEND_DATASTORE=datastore +BROKER_URL=amqp://guest:guest@rabbitmq:5672/ +CELERY_BEAT_SCHEDULER=celery.beat:PersistentScheduler +ASYNC_SIGNALS=True + +SITEURL={siteurl}/ + +ALLOWED_HOSTS="['django', '{hostname}']" + +# Data Uploader +DEFAULT_BACKEND_UPLOADER=geonode.importer +TIME_ENABLED=True +MOSAIC_ENABLED=False +HAYSTACK_SEARCH=False +HAYSTACK_ENGINE_URL=http://elasticsearch:9200/ +HAYSTACK_ENGINE_INDEX_NAME=haystack +HAYSTACK_SEARCH_RESULTS_PER_PAGE=200 + +# ################# +# nginx +# HTTPD Server +# ################# +GEONODE_LB_HOST_IP=django +GEONODE_LB_PORT=8000 +NGINX_BASE_URL={siteurl} + +# IP or domain name and port where the server can be reached on HTTPS (leave HOST empty if you want to use HTTP only) +# port where the server can be reached on HTTPS +HTTP_HOST={http_host} +HTTPS_HOST={https_host} + +HTTP_PORT=80 +HTTPS_PORT=443 + +# Let's Encrypt certificates for https encryption. You must have a domain name as HTTPS_HOST (doesn't work +# with an ip) and it must be reachable from the outside. This can be one of the following : +# disabled : we do not get a certificate at all (a placeholder certificate will be used) +# staging : we get staging certificates (are invalid, but allow to test the process completely and have much higher limit rates) +# production : we get a normal certificate (default) +LETSENCRYPT_MODE={letsencrypt_mode} +# LETSENCRYPT_MODE=staging +# LETSENCRYPT_MODE=production + +RESOLVER=127.0.0.11 + +# ################# +# geoserver +# ################# +GEOSERVER_LB_HOST_IP=geoserver +GEOSERVER_LB_PORT=8080 +GEOSERVER_WEB_UI_LOCATION={siteurl}/geoserver/ +GEOSERVER_PUBLIC_LOCATION={siteurl}/geoserver/ +GEOSERVER_LOCATION=http://${GEOSERVER_LB_HOST_IP}:${GEOSERVER_LB_PORT}/geoserver/ +GEOSERVER_ADMIN_USER=admin +GEOSERVER_ADMIN_PASSWORD={geoserverpwd} + +OGC_REQUEST_TIMEOUT=30 +OGC_REQUEST_MAX_RETRIES=1 +OGC_REQUEST_BACKOFF_FACTOR=0.3 +OGC_REQUEST_POOL_MAXSIZE=10 +OGC_REQUEST_POOL_CONNECTIONS=10 + +# Java Options & Memory +ENABLE_JSONP=true +outFormat=text/javascript +GEOSERVER_JAVA_OPTS=-Djava.awt.headless=true -Xms4G -Xmx4G -Dgwc.context.suffix=gwc -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/var/log/jvm.log -XX:PerfDataSamplingInterval=500 -XX:SoftRefLRUPolicyMSPerMB=36000 -XX:-UseGCOverheadLimit -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -Duser.timezone=GMT -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL={siteurl}/geoserver/pdf -DALLOW_ENV_PARAMETRIZATION=true -Xbootclasspath/a:/usr/local/tomcat/webapps/geoserver/WEB-INF/lib/marlin-0.9.3-Unsafe.jar -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine + +# ################# +# Security +# ################# +# Admin Settings +# +# ADMIN_PASSWORD is used to overwrite the GeoNode admin password **ONLY** the first time +# GeoNode is run. If you need to overwrite it again, you need to set the env var FORCE_REINIT, +# otherwise the invoke updateadmin task will be skipped and the current password already stored +# in DB will honored. + +ADMIN_USERNAME=admin +ADMIN_PASSWORD={geonodepwd} +ADMIN_EMAIL={email} + +# EMAIL Notifications +EMAIL_ENABLE=False +DJANGO_EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +DJANGO_EMAIL_HOST=localhost +DJANGO_EMAIL_PORT=25 +DJANGO_EMAIL_HOST_USER= +DJANGO_EMAIL_HOST_PASSWORD= +DJANGO_EMAIL_USE_TLS=False +DJANGO_EMAIL_USE_SSL=False +DEFAULT_FROM_EMAIL='{email}' # eg Company + +# Session/Access Control +LOCKDOWN_GEONODE=False +X_FRAME_OPTIONS="SAMEORIGIN" +SESSION_EXPIRED_CONTROL_ENABLED=True +DEFAULT_ANONYMOUS_VIEW_PERMISSION=True +DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION=True + +CORS_ALLOW_ALL_ORIGINS=True +GEOSERVER_CORS_ENABLED=True +GEOSERVER_CORS_ALLOWED_ORIGINS=* +GEOSERVER_CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,HEAD,OPTIONS +GEOSERVER_CORS_ALLOWED_HEADERS=* + +# Users Registration +ACCOUNT_OPEN_SIGNUP=True +ACCOUNT_EMAIL_REQUIRED=True +ACCOUNT_APPROVAL_REQUIRED=False +ACCOUNT_CONFIRM_EMAIL_ON_GET=False +ACCOUNT_EMAIL_VERIFICATION=none +ACCOUNT_EMAIL_CONFIRMATION_EMAIL=False +ACCOUNT_EMAIL_CONFIRMATION_REQUIRED=False +ACCOUNT_AUTHENTICATION_METHOD=username_email +AUTO_ASSIGN_REGISTERED_MEMBERS_TO_REGISTERED_MEMBERS_GROUP_NAME=True + +# OAuth2 +OAUTH2_API_KEY= +OAUTH2_CLIENT_ID={clientid} +OAUTH2_CLIENT_SECRET={clientsecret} + +# GeoNode APIs +API_LOCKDOWN=False +TASTYPIE_APIKEY= + +# ################# +# Production and +# Monitoring +# ################# +DEBUG={debug} + +SECRET_KEY='{secret_key}' + +STATIC_ROOT=/mnt/volumes/statics/static/ +MEDIA_ROOT=/mnt/volumes/statics/uploaded/ +GEOIP_PATH=/mnt/volumes/statics/geoip.db + +CACHE_BUSTING_STATIC_ENABLED=False + +MEMCACHED_ENABLED=False +MEMCACHED_BACKEND=django.core.cache.backends.memcached.MemcachedCache +MEMCACHED_LOCATION=memcached:11211 +MEMCACHED_LOCK_EXPIRE=3600 +MEMCACHED_LOCK_TIMEOUT=10 +# +# Options for memcached binary, e.g. -vvv to log all requests and cache hits +# +MEMCACHED_OPTIONS= + +MAX_DOCUMENT_SIZE=200 +CLIENT_RESULTS_LIMIT=5 +API_LIMIT_PER_PAGE=1000 + +# GIS Client +GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY=mapstore +MAPBOX_ACCESS_TOKEN= +BING_API_KEY= +GOOGLE_API_KEY= + +# Monitoring +MONITORING_ENABLED=False +MONITORING_DATA_TTL=365 +USER_ANALYTICS_ENABLED=True +USER_ANALYTICS_GZIP=True +CENTRALIZED_DASHBOARD_ENABLED=False +MONITORING_SERVICE_NAME=local-geonode +MONITORING_HOST_NAME=geonode + +# Other Options/Contribs +MODIFY_TOPICCATEGORY=True +AVATAR_GRAVATAR_SSL=True +EXIF_ENABLED=True +CREATE_LAYER=True +FAVORITE_ENABLED=True + +# Advanced Workflow +RESOURCE_PUBLISHING=False +ADMIN_MODERATE_UPLOADS=False + +# LDAP +LDAP_ENABLED=False +LDAP_SERVER_URL=ldap:// +LDAP_BIND_DN=uid=ldapinfo,cn=users,dc=ad,dc=example,dc=org +LDAP_BIND_PASSWORD= +LDAP_USER_SEARCH_DN=dc=ad,dc=example,dc=org +LDAP_USER_SEARCH_FILTERSTR=(&(uid=%(user)s)(objectClass=person)) +LDAP_GROUP_SEARCH_DN=cn=groups,dc=ad,dc=example,dc=org +LDAP_GROUP_SEARCH_FILTERSTR=(|(cn=abt1)(cn=abt2)(cn=abt3)(cn=abt4)(cn=abt5)(cn=abt6)) +LDAP_GROUP_PROFILE_MEMBER_ATTR=uniqueMember + +# CELERY + +# expressed in KB +# CELERY__MAX_MEMORY_PER_CHILD="200000" +# ## +# Note right autoscale value must coincide with worker concurrency value +# CELERY__AUTOSCALE_VALUES="15,10" +# CELERY__WORKER_CONCURRENCY="10" +# ## +# CELERY__OPTS="--without-gossip --without-mingle -Ofair -B -E" +# CELERY__BEAT_SCHEDULE="/mnt/volumes/statics/celerybeat-schedule" +# CELERY__LOG_LEVEL="INFO" +# CELERY__LOG_FILE="/var/log/celery.log" +# CELERY__WORKER_NAME="worker1@%h" + +# PostgreSQL +POSTGRESQL_MAX_CONNECTIONS=200 + +# Common containers restart policy +RESTART_POLICY_CONDITION="on-failure" +RESTART_POLICY_DELAY="5s" +RESTART_POLICY_MAX_ATTEMPTS="3" +RESTART_POLICY_WINDOW=120s + +DEFAULT_MAX_UPLOAD_SIZE=5368709120 +DEFAULT_MAX_PARALLEL_UPLOADS_PER_USER=5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4137f8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,72 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +# env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environment +venv/ +.venv/ + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Django stuff: +*.log +celerybeat-schedule.* + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Downstream Project +downloaded/ +uploaded/ +static_root/ +development.db +development.db-journal +local_settings.py + +.idea/ +.vscode/ +.vagrant/ +Vagrantfile +/.env diff --git a/.override_dev_env.sample b/.override_dev_env.sample new file mode 100644 index 0000000..939de2a --- /dev/null +++ b/.override_dev_env.sample @@ -0,0 +1,103 @@ +export DEBUG=True + +export SECRET_KEY="myv-y4#7j-d*p-__@j#*3z@!y24fz8%^z2v6atuy4bo9vqr1_a" + +export SITEURL=http://localhost:8000/ +export ALLOWED_HOSTS="['localhost',]" + +export GEONODE_INSTANCE_NAME=geonode +export DJANGO_SETTINGS_MODULE=urban.settings +export GEONODE_DATABASE=urban +export GEONODE_DATABASE_PASSWORD=geonode +export GEONODE_GEODATABASE=urban_data +export GEONODE_GEODATABASE_PASSWORD=geonode + +export DATABASE_URL=postgis://urban:geonode@localhost:5432/urban +export GEODATABASE_URL=postgis://urban_data:geonode@localhost:5432/urban_data +export DEFAULT_BACKEND_DATASTORE=datastore + +export GEOSERVER_WEB_UI_LOCATION=http://localhost:8080/geoserver/ +export GEOSERVER_PUBLIC_LOCATION=http://localhost:8080/geoserver/ +export GEOSERVER_LOCATION=http://localhost:8080/geoserver/ +export GEOSERVER_ADMIN_USER=admin +export GEOSERVER_ADMIN_PASSWORD=geoserver + +export OGC_REQUEST_TIMEOUT=30 +export OGC_REQUEST_MAX_RETRIES=1 +export OGC_REQUEST_BACKOFF_FACTOR=0.3 +export OGC_REQUEST_POOL_MAXSIZE=10 +export OGC_REQUEST_POOL_CONNECTIONS=10 + +export DEFAULT_BACKEND_UPLOADER=geonode.importer +export TIME_ENABLED=True +export MOSAIC_ENABLED=False + +export ADMIN_PASSWORD=admin +export ADMIN_EMAIL=admin@localhost + +export EMAIL_ENABLE=False +export DJANGO_EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +export DJANGO_EMAIL_HOST=localhost +export DJANGO_EMAIL_PORT=25 +export DJANGO_EMAIL_HOST_USER= +export DJANGO_EMAIL_HOST_PASSWORD= +export DJANGO_EMAIL_USE_TLS=False +export DJANGO_EMAIL_USE_SSL=False +export DEFAULT_FROM_EMAIL="GeoNode " + +export LOCKDOWN_GEONODE=False +export X_FRAME_OPTIONS=SAMEORIGIN +export SESSION_EXPIRED_CONTROL_ENABLED=True +export DEFAULT_ANONYMOUS_VIEW_PERMISSION=True +export DEFAULT_ANONYMOUS_DOWNLOAD_PERMISSION=True + +export CORS_ALLOW_ALL_ORIGINS=True +export GEOSERVER_CORS_ENABLED=True +export GEOSERVER_CORS_ALLOWED_ORIGINS=* +export GEOSERVER_CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,HEAD,OPTIONS +export GEOSERVER_CORS_ALLOWED_HEADERS=* + +export ACCOUNT_OPEN_SIGNUP=True +export ACCOUNT_EMAIL_REQUIRED=True +export ACCOUNT_APPROVAL_REQUIRED=False +export ACCOUNT_CONFIRM_EMAIL_ON_GET=False +export ACCOUNT_EMAIL_VERIFICATION=none +export ACCOUNT_EMAIL_CONFIRMATION_EMAIL=False +export ACCOUNT_EMAIL_CONFIRMATION_REQUIRED=False +export ACCOUNT_AUTHENTICATION_METHOD=username_email + +export OAUTH2_API_KEY= +export OAUTH2_CLIENT_ID=Jrchz2oPY3akmzndmgUTYrs9gczlgoV20YPSvqaV +export OAUTH2_CLIENT_SECRET=rCnp5txobUo83EpQEblM8fVj3QT5zb5qRfxNsuPzCqZaiRyIoxM4jdgMiZKFfePBHYXCLd7B8NlkfDBY9HKeIQPcy5Cp08KQNpRHQbjpLItDHv12GvkSeXp6OxaUETv3 + +export API_LOCKDOWN=False +export TASTYPIE_APIKEY= + +export CACHE_BUSTING_STATIC_ENABLED=False + +export MAX_DOCUMENT_SIZE=2 +export CLIENT_RESULTS_LIMIT=5 +export API_LIMIT_PER_PAGE=1000 + +export GEONODE_CLIENT_LAYER_PREVIEW_LIBRARY=mapstore +export MAPBOX_ACCESS_TOKEN= +export BING_API_KEY= +export GOOGLE_API_KEY= + +export MONITORING_ENABLED=False +export MONITORING_DATA_TTL=365 +export USER_ANALYTICS_ENABLED=True +export USER_ANALYTICS_GZIP=True +export CENTRALIZED_DASHBOARD_ENABLED=False +export MONITORING_SERVICE_NAME=local-geonode +export MONITORING_HOST_NAME=geonode + +export MODIFY_TOPICCATEGORY=True +export AVATAR_GRAVATAR_SSL=True +export AVATAR_PROVIDERS='avatar.providers.PrimaryAvatarProvider','avatar.providers.GravatarAvatarProvider','avatar.providers.DefaultAvatarProvider' +export EXIF_ENABLED=True +export CREATE_LAYER=True +export FAVORITE_ENABLED=True + +export DEFAULT_MAX_UPLOAD_SIZE=5368709120 +export DEFAULT_MAX_PARALLEL_UPLOADS_PER_USER=5 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..18e0d11 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +FROM geonode/geonode-base:latest-ubuntu-22.04 +LABEL GeoNode development team + +RUN mkdir -p /usr/src/urban + +RUN apt-get update -y && apt-get install curl wget unzip gnupg2 locales -y + +RUN sed -i -e 's/# C.UTF-8 UTF-8/C.UTF-8 UTF-8/' /etc/locale.gen && \ + locale-gen +ENV LC_ALL C.UTF-8 +ENV LANG C.UTF-8 + +# add bower and grunt command +COPY src /usr/src/urban/ +WORKDIR /usr/src/urban + +#COPY src/monitoring-cron /etc/cron.d/monitoring-cron +#RUN chmod 0644 /etc/cron.d/monitoring-cron +#RUN crontab /etc/cron.d/monitoring-cron +#RUN touch /var/log/cron.log +#RUN service cron start + +COPY src/wait-for-databases.sh /usr/bin/wait-for-databases +RUN chmod +x /usr/bin/wait-for-databases +RUN chmod +x /usr/src/urban/tasks.py \ + && chmod +x /usr/src/urban/entrypoint.sh + +COPY src/celery.sh /usr/bin/celery-commands +RUN chmod +x /usr/bin/celery-commands + +COPY src/celery-cmd /usr/bin/celery-cmd +RUN chmod +x /usr/bin/celery-cmd + +# Install "geonode-contribs" apps +# RUN cd /usr/src; git clone https://github.com/GeoNode/geonode-contribs.git -b master +# Install logstash and centralized dashboard dependencies +# RUN cd /usr/src/geonode-contribs/geonode-logstash; pip install --upgrade -e . \ +# cd /usr/src/geonode-contribs/ldap; pip install --upgrade -e . + +RUN yes w | pip install --src /usr/src -r requirements.txt &&\ + yes w | pip install -e . + +# Cleanup apt update lists +RUN apt-get autoremove --purge &&\ + apt-get clean &&\ + rm -rf /var/lib/apt/lists/* + +# Export ports +EXPOSE 8000 + +# We provide no command or entrypoint as this image can be used to serve the django project or run celery tasks +# ENTRYPOINT /usr/src/urban/entrypoint.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..0b7d057 --- /dev/null +++ b/README.md @@ -0,0 +1,396 @@ +# Urban + +GeoNode template project. Generates a django project with GeoNode support. + +## Table of Contents + +- [Quick Docker Start](#quick-docker-start) +- [Developer Workshop](#developer-workshop) +- [Create a custom project](#create-a-custom-project) +- [Start your server using Docker](#start-your-server-using-docker) +- [Run the instance in development mode](#run-the-instance-in-development-mode) +- [Run the instance on a public site](#run-the-instance-on-a-public-site) +- [Stop the Docker Images](#stop-the-docker-images) +- [Backup and Restore from Docker Images](#backup-and-restore-the-docker-images) +- [Recommended: Track your changes](#recommended-track-your-changes) +- [Hints: Configuring `requirements.txt`](#hints-configuring-requirementstxt) + +## Quick Docker Start + + ```bash + python3.10 -m venv ~/.venvs/project_name + source ~/.venvs/urban/bin/activate + + pip install Django==3.2.* + + mkdir ~/project_name + ``` + + ```bash + GN_VERSION=master + + django-admin startproject --template=https://github.com/GeoNode/geonode-project/archive/refs/tags/$GN_VERSION.zip -e py,sh,md,rst,json,yml,ini,env,sample,properties -n monitoring-cron -n Dockerfile project_name ~/project_name + ``` + + ```bash + cd ~/project_name + python create-envfile.py + ``` +`create-envfile.py` accepts the following arguments: + +- `--https`: Enable SSL. It's disabled by default +- `--env_type`: + - When set to `prod` `DEBUG` is disabled and the creation of a valid `SSL` is requested to Letsencrypt's ACME server + - When set to `test` `DEBUG` is disabled and a test `SSL` certificate is generated for local testing + - When set to `dev` `DEBUG` is enabled and no `SSL` certificate is generated +- `--hostname`: The URL that whill serve GeoNode (`localhost` by default) +- `--email`: The administrator's email. Notice that a real email and a valid SMPT configurations are required if `--env_type` is seto to `prod`. Letsencrypt uses to email for issuing the SSL certificate +- `--geonodepwd`: GeoNode's administrator password. A random value is set if left empty +- `--geoserverpwd`: GeoNode's administrator password. A random value is set if left empty +- `--pgpwd`: PostgreSQL's administrator password. A random value is set if left empty +- `--dbpwd`: GeoNode DB user role's password. A random value is set if left empty +- `--geodbpwd`: GeoNode data DB user role's password. A random value is set if left empty +- `--clientid`: Client id of Geoserver's GeoNode Oauth2 client. A random value is set if left empty +- `--clientsecret`: Client secret of Geoserver's GeoNode Oauth2 client. A random value is set if left empty +```bash + docker compose build + docker compose up -d +``` + +## Developer Workshop + +Available at + + ```bash + http://geonode.org/dev-workshop + ``` + +## Create a custom project + +**NOTE**: *You can call your geonode project whatever you like **except 'geonode'**. Follow the naming conventions for python packages (generally lower case with underscores (``_``). In the examples below, replace ``urban`` with whatever you would like to name your project.* + +To setup your project follow these instructions: + +1. Generate the project + + ```bash + git clone https://github.com/GeoNode/geonode-project.git -b + source /usr/share/virtualenvwrapper/virtualenvwrapper.sh + mkvirtualenv --python=/usr/bin/python3 urban + pip install Django==3.2.16 + + django-admin startproject --template=./geonode-project -e py,sh,md,rst,json,yml,ini,env,sample,properties -n monitoring-cron -n Dockerfile urban + + cd urban + ``` + +2. Create the .env file + + An `.env` file is requird to run the application. It can be created from the `.env.sample` either manually or with the create-envfile.py script. + + The script accepts several parameters to create the file, in detail: + + - *hostname*: e.g. master.demo.geonode.org, default localhost + - *https*: (boolean), default value is False + - *email*: Admin email (this is required if https is set to True since a valid email is required by Letsencrypt certbot) + - *env_type*: `prod`, `test` or `dev`. It will set the `DEBUG` variable to `False` (`prod`, `test`) or `True` (`dev`) + - *geonodepwd*: GeoNode admin password (required inside the .env) + - *geoserverpwd*: Geoserver admin password (required inside the .env) + - *pgpwd*: PostgreSQL password (required inside the .env) + - *dbpwd*: GeoNode DB user password (required inside the .env) + - *geodbpwd*: Geodatabase user password (required inside the .env) + - *clientid*: Oauth2 client id (required inside the .env) + - *clientsecret*: Oauth2 client secret (required inside the .env) + - *secret key*: Django secret key (required inside the .env) + - *sample_file*: absolute path to a env_sample file used to create the env_file. If not provided, the one inside the GeoNode project is used. + - *file*: absolute path to a json file that contains all the above configuration + + **NOTE:** + - if the same configuration is passed in the json file and as an argument, the CLI one will overwrite the one in the JSON file + - If some value is not provided, a random string is used + + Example USAGE + + ```bash + python create-envfile.py -f /opt/core/geonode-project/file.json \ + --hostname localhost \ + --https \ + --email random@email.com \ + --geonodepwd gn_password \ + --geoserverpwd gs_password \ + --pgpwd pg_password \ + --dbpwd db_password \ + --geodbpwd _db_password \ + --clientid 12345 \ + --clientsecret abc123 + ``` + + Example JSON expected: + + ```JSON + { + "hostname": "value", + "https": "value", + "email": "value", + "geonodepwd": "value", + "geoserverpwd": "value", + "pgpwd": "value", + "dbpwd": "value", + "geodbpwd": "value", + "clientid": "value", + "clientsecret": "value" + } + ``` + +### Start your server +*Skip this part if you want to run the project using Docker instead* see [Start your server using Docker](#start-your-server-using-docker) + +1. Setup the Python Dependencies + + **NOTE**: *Important: modify your `requirements.txt` file, by adding the `GeoNode` branch before continue!* + + (see [Hints: Configuring `requirements.txt`](#hints-configuring-requirementstxt)) + + ```bash + cd src + pip install -r requirements.txt --upgrade + pip install -e . --upgrade + + # Install GDAL Utilities for Python + pip install pygdal=="`gdal-config --version`.*" + + # Dev scripts + mv ../.override_dev_env.sample ../.override_dev_env + mv manage_dev.sh.sample manage_dev.sh + mv paver_dev.sh.sample paver_dev.sh + + source ../.override_dev_env + + # Using the Default Settings + sh ./paver_dev.sh reset + sh ./paver_dev.sh setup + sh ./paver_dev.sh sync + sh ./paver_dev.sh start + ``` + +2. Access GeoNode from browser + + **NOTE**: default admin user is ``admin`` (with pw: ``admin``) + + ```bash + http://localhost:8000/ + ``` + +### Start your server using Docker + +You need Docker 1.12 or higher, get the latest stable official release for your platform. +Once you have the project configured run the following command from the root folder of the project. + +1. Run `docker-compose` to start it up (get a cup of coffee or tea while you wait) + + ```bash + docker-compose build --no-cache + docker-compose up -d + ``` + + ```bash + set COMPOSE_CONVERT_WINDOWS_PATHS=1 + ``` + + before running `docker-compose up` + +2. Access the site on http://localhost/ + +## Run the instance in development mode + +### Use dedicated docker-compose files while developing + +**NOTE**: In this example we are going to keep localhost as the target IP for GeoNode + + ```bash + docker-compose -f docker-compose.development.yml -f docker-compose.development.override.yml up + ``` + +## Run the instance on a public site + +### Preparation of the image (First time only) + +**NOTE**: In this example we are going to publish to the public IP http://123.456.789.111 + +```bash +vim .env + --> replace localhost with 123.456.789.111 everywhere +``` + +### Startup the image + +```bash +docker-compose up --build -d +``` + +### Stop the Docker Images + +```bash +docker-compose stop +``` + +### Fully Wipe-out the Docker Images + +**WARNING**: This will wipe out all the repositories created until now. + +**NOTE**: The images must be stopped first + +```bash +docker system prune -a +``` + +## Backup and Restore from Docker Images + +### Run a Backup + +```bash +SOURCE_URL=$SOURCE_URL TARGET_URL=$TARGET_URL ./urban/br/backup.sh $BKP_FOLDER_NAME +``` + +- BKP_FOLDER_NAME: + Default value = backup_restore + Shared Backup Folder name. + The scripts assume it is located on "root" e.g.: /$BKP_FOLDER_NAME/ + +- SOURCE_URL: + Source Server URL, the one generating the "backup" file. + +- TARGET_URL: + Target Server URL, the one which must be synched. + +e.g.: + +```bash +docker exec -it django4urban sh -c 'SOURCE_URL=$SOURCE_URL TARGET_URL=$TARGET_URL ./urban/br/backup.sh $BKP_FOLDER_NAME' +``` + +### Run a Restore + +```bash +SOURCE_URL=$SOURCE_URL TARGET_URL=$TARGET_URL ./urban/br/restore.sh $BKP_FOLDER_NAME +``` + +- BKP_FOLDER_NAME: + Default value = backup_restore + Shared Backup Folder name. + The scripts assume it is located on "root" e.g.: /$BKP_FOLDER_NAME/ + +- SOURCE_URL: + Source Server URL, the one generating the "backup" file. + +- TARGET_URL: + Target Server URL, the one which must be synched. + +e.g.: + +```bash +docker exec -it django4urban sh -c 'SOURCE_URL=$SOURCE_URL TARGET_URL=$TARGET_URL ./urban/br/restore.sh $BKP_FOLDER_NAME' +``` + +## Recommended: Track your changes + +Step 1. Install Git (for Linux, Mac or Windows). + +Step 2. Init git locally and do the first commit: + +```bash +git init +git add * +git commit -m "Initial Commit" +``` + +Step 3. Set up a free account on github or bitbucket and make a copy of the repo there. + +## Hints: Configuring `requirements.txt` + +You may want to configure your requirements.txt, if you are using additional or custom versions of python packages. For example + +```python +Django==3.2.16 +git+git://github.com//geonode.git@ +``` + +## Increasing PostgreSQL Max connections + +In case you need to increase the PostgreSQL Max Connections , you can modify +the **POSTGRESQL_MAX_CONNECTIONS** variable in **.env** file as below: + +``` +POSTGRESQL_MAX_CONNECTIONS=200 +``` + +In this case PostgreSQL will run accepting 200 maximum connections. + +## Test project generation and docker-compose build Vagrant usage + +Testing with [vagrant](https://www.vagrantup.com/docs) works like this: +What vagrant does: + +Starts a vm for test on docker swarm: + - configures a GeoNode project from template every time from your working directory (so you can develop directly on geonode-project). + - exposes service on localhost port 8888 + - rebuilds everytime everything with cache [1] to avoid banning from docker hub with no login. + - starts, reboots to check if docker services come up correctly after reboot. + +```bash +vagrant plugin install vagrant-reload +#test things for docker-compose +vagrant up +# check services are up upon reboot +vagrant ssh geonode-compose -c 'docker ps' +``` + +Test geonode on [http://localhost:8888/](http://localhost:8888/) + +To clean up things and delete the vagrant box: + +```bash +vagrant destroy -f +``` + +## Test project generation and Docker swarm build on vagrant + +What vagrant does: + +Starts a vm for test on docker swarm: + - configures a GeoNode project from template every time from your working directory (so you can develop directly on geonode-project). + - exposes service on localhost port 8888 + - rebuilds everytime everything with cache [1] to avoid banning from docker hub with no login. + - starts, reboots to check if docker services come up correctly after reboot. + +To test on a docker swarm enable vagrant box: + +```bash +vagrant up +VAGRANT_VAGRANTFILE=Vagrantfile.stack vagrant up +# check services are up upon reboot +VAGRANT_VAGRANTFILE=Vagrantfile.stack vagrant ssh geonode-compose -c 'docker service ls' +``` + +Test geonode on [http://localhost:8888/](http://localhost:8888/) +Again, to clean up things and delete the vagrant box: + +```bash +VAGRANT_VAGRANTFILE=Vagrantfile.stack vagrant destroy -f +``` + +for direct deveolpment on geonode-project after first `vagrant up` to rebuild after changes to project, you can do `vagrant reload` like this: + +```bash +vagrant up +``` + +What vagrant does (swarm or comnpose cases): + +Starts a vm for test on plain docker service with docker-compose: + - configures a GeoNode project from template every time from your working directory (so you can develop directly on geonode-project). + - rebuilds everytime everything with cache [1] to avoid banning from docker hub with no login. + - starts, reboots. + +[1] to achieve `docker-compose build --no-cache` just destroy vagrant boxes `vagrant destroy -f` + diff --git a/Vagrantfile.stack b/Vagrantfile.stack new file mode 100644 index 0000000..f917d3e --- /dev/null +++ b/Vagrantfile.stack @@ -0,0 +1,58 @@ +$geonode_source_path='.' + +$script1 = <<-'SCRIPT' +sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +sudo chmod +x /usr/local/bin/docker-compose +sudo apt-get -f install \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg \ + lsb-release +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +sudo apt-get update +sudo apt-get -y install docker-ce docker-ce-cli containerd.io pwgen python3-pip python3-virtualenv virtualenvwrapper +sudo adduser vagrant docker +SCRIPT + +$script2 = <<-'SCRIPT' +if [ -d "/home/vagrant/generated-project" ]; then + cd /home/vagrant/generated-project + docker service rm geonode-stack + cd .. + rm -rf /home/vagrant/generated-project +fi +rm -rf /home/vagrant/geonode-project +SCRIPT + +$script4 = <<-'SCRIPT' +sudo reboot +SCRIPT + +Vagrant.configure(2) do |config| + boxes = { + 'geonode-swarm' => 'ubuntu/focal64' + } + machine_id = 0 + boxes.each do | key, value | + config.vm.network "forwarded_port", guest: 80, host: 8888 + config.vm.provider "virtualbox" do |v| + v.memory = 8132 + v.cpus = 4 + v.name = "geonode-vm" + end + config.vm.box = "#{value}" + machine_id = machine_id + 1 + config.vm.define "#{key}" do |node| + node.vm.hostname = "#{key}" + config.vm.provision "shell", inline: $script1, privileged: false + config.vm.provision "shell", inline: $script2, run: 'always', privileged: true + config.vm.provision "file", source: $geonode_source_path, destination: "$HOME/geonode-project", run: 'always' + config.vm.provision :shell, path: "generated-project-vagrant-swarm.sh", run: 'always', privileged: false + config.vm.provision "shell", inline: $script3, run: 'always', privileged: false + end + end +end \ No newline at end of file diff --git a/create-envfile.py b/create-envfile.py new file mode 100644 index 0000000..e061201 --- /dev/null +++ b/create-envfile.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# Copyright (C) 2022 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +import argparse +import json +import logging +import os +import random +import re +import string +import sys +import ast + +dir_path = os.path.dirname(os.path.realpath(__file__)) + +logger = logging.getLogger() +handler = logging.StreamHandler(sys.stdout) +logger.setLevel(logging.INFO) +formatter = logging.Formatter("%(levelname)s - %(message)s") +handler.setFormatter(formatter) +logger.addHandler(handler) + + +def shuffle(chars): + chars_as_list = list(chars) + random.shuffle(chars_as_list) + return "".join(chars_as_list) + + +_simple_chars = shuffle(string.ascii_letters + string.digits) +_strong_chars = shuffle( + string.ascii_letters + + string.digits + + string.punctuation.replace('"', "").replace("'", "").replace("`", "") +) + + +def generate_env_file(args): + # validity checks + if not os.path.exists(args.sample_file): + logger.error(f"File does not exists {args.sample_file}") + raise FileNotFoundError + + if args.file and not os.path.isfile(args.file): + logger.error(f"File does not exists: {args.file}") + raise FileNotFoundError + + if args.https and not args.email: + raise Exception("With HTTPS enabled, the email parameter is required") + + _sample_file = None + with open(args.sample_file, "r+") as sample_file: + _sample_file = sample_file.read() + + if not _sample_file: + raise Exception("Sample file is empty!") + + def _get_vals_to_replace(args): + _config = ["sample_file", "file", "env_type", "https", "email"] + _jsfile = {} + if args.file: + with open(args.file) as _json_file: + _jsfile = json.load(_json_file) + + _vals_to_replace = { + key: _jsfile.get(key, val) + for key, val in vars(args).items() + if key not in _config + } + tcp = ( + "https" + if ast.literal_eval(f"{_jsfile.get('https', args.https)}".capitalize()) + else "http" + ) + + _vals_to_replace["http_host"] = ( + _jsfile.get("hostname", args.hostname) if tcp == "http" else "" + ) + _vals_to_replace["https_host"] = ( + _jsfile.get("hostname", args.hostname) if tcp == "https" else "" + ) + + _vals_to_replace[ + "siteurl" + ] = f"{tcp}://{_jsfile.get('hostname', args.hostname)}" + _vals_to_replace["secret_key"] = _jsfile.get( + "secret_key", args.secret_key + ) or "".join(random.choice(_strong_chars) for _ in range(50)) + _vals_to_replace["letsencrypt_mode"] = ( + "disabled" + if not _vals_to_replace.get("https_host") + else "staging" + if _jsfile.get("env_type", args.env_type) in ["test"] + else "production" + ) + _vals_to_replace["debug"] = ( + False + if _jsfile.get("env_type", args.env_type) in ["prod", "test"] + else True + ) + _vals_to_replace["email"] = _jsfile.get("email", args.email) + + if tcp == "https" and not _vals_to_replace["email"]: + raise Exception("With HTTPS enabled, the email parameter is required") + + return {**_jsfile, **_vals_to_replace} + + for key, val in _get_vals_to_replace(args).items(): + _val = val or "".join(random.choice(_simple_chars) for _ in range(15)) + if isinstance(val, bool) or key in ["email", "http_host", "https_host"]: + _val = str(val) + _sample_file = re.sub( + "{" + key + "}", + lambda _: _val, + _sample_file, + ) + + with open(f"{dir_path}/.env", "w+") as output_env: + output_env.write(_sample_file) + logger.info(f".env file created: {dir_path}/.env") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + prog="ENV file builder", + description="Tool for generate environment file automatically. The information can be passed or via CLI or via JSON file ( --file /path/env.json)", + usage="python create-envfile.py localhost -f /path/to/json/file.json", + allow_abbrev=False + ) + parser.add_argument( + "--noinput", + "--no-input", + action="store_false", + dest="confirmation", + help=("skips prompting for confirmation."), + ) + parser.add_argument( + "-hn", + "--hostname", + help=f"Host name, default localhost", + default="localhost", + ) + + # expected path as a value + parser.add_argument( + "-sf", + "--sample_file", + help=f"Path of the sample file to use as a template. Default is: {dir_path}/.env.sample", + default=f"{dir_path}/.env.sample", + ) + parser.add_argument( + "-f", + "--file", + help="absolute path of the file with the configuration. Note: we expect that the keys of the dictionary have the same name as the CLI params", + ) + # booleans + parser.add_argument( + "--https", action="store_true", default=False, help="If provided, https is used" + ) + # strings + parser.add_argument( + "--email", help="Admin email, this field is required if https is enabled" + ) + + parser.add_argument("--geonodepwd", help="GeoNode admin password") + parser.add_argument("--geoserverpwd", help="Geoserver admin password") + parser.add_argument("--pgpwd", help="PostgreSQL password") + parser.add_argument("--dbpwd", help="GeoNode DB user password") + parser.add_argument("--geodbpwd", help="Geodatabase user password") + parser.add_argument("--clientid", help="Oauth2 client id") + parser.add_argument("--clientsecret", help="Oauth2 client secret") + parser.add_argument("--secret_key", help="Django Secret Key") + + parser.add_argument( + "--env_type", + help="Development/production or test", + choices=["prod", "test", "dev"], + default="prod", + ) + + args = parser.parse_args() + + if not args.confirmation: + generate_env_file(args) + else: + overwrite_env = input( + "This action will overwrite any existing .env file. Do you wish to continue? (y/n)" + ) + if overwrite_env not in ["y", "n"]: + logger.error("Please enter a valid response") + if overwrite_env == "y": + generate_env_file(args) diff --git a/dev_config.yml b/dev_config.yml new file mode 100644 index 0000000..0f7ec76 --- /dev/null +++ b/dev_config.yml @@ -0,0 +1,8 @@ +--- +GEOSERVER_URL: "https://artifacts.geonode.org/geoserver/2.24.2/geoserver.war" +DATA_DIR_URL: "https://artifacts.geonode.org/geoserver/2.24.2/geonode-geoserver-ext-web-app-data.zip" +JETTY_RUNNER_URL: "https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-runner/9.4.31.v20200723/jetty-runner-9.4.31.v20200723.jar" +WINDOWS: + py2exe: "http://downloads.sourceforge.net/project/py2exe/py2exe/0.6.9/py2exe-0.6.9.win32-py2.7.exe" + pyproj: "https://pyproj.googlecode.com/files/pyproj-1.9.3.win32-py2.7.exe" + lxml: "https://pypi.python.org/packages/2.7/l/lxml/lxml-3.6.0.win32-py2.7.exe" diff --git a/docker-build.sh b/docker-build.sh new file mode 100755 index 0000000..64fba5a --- /dev/null +++ b/docker-build.sh @@ -0,0 +1 @@ +docker-compose build --no-cache; docker-compose stop; docker-compose up -d; docker system prune -a \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..384785f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,200 @@ +version: '3.9' + +# Common Django template for GeoNode and Celery services below +x-common-django: + &default-common-django + image: ${COMPOSE_PROJECT_NAME}/geonode:${GEONODE_BASE_IMAGE_VERSION} + restart: unless-stopped + env_file: + - .env + volumes: + # - './src:/usr/src/urban' + - statics:/mnt/volumes/statics + - geoserver-data-dir:/geoserver_data/data + - backup-restore:/backup_restore + - data:/data + - tmp:/tmp + depends_on: + db: + condition: service_healthy + +services: + + # Our custom django application. It includes Geonode. + django: + << : *default-common-django + build: + context: ./ + dockerfile: Dockerfile + container_name: django4${COMPOSE_PROJECT_NAME} + healthcheck: + test: "curl -m 10 --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://django:8000/" + start_period: 60s + interval: 60s + timeout: 10s + retries: 2 + environment: + - IS_CELERY=False + entrypoint: ["/usr/src/urban/entrypoint.sh"] + command: "uwsgi --ini /usr/src/urban/uwsgi.ini" + + # Celery worker that executes celery tasks created by Django. + celery: + << : *default-common-django + container_name: celery4${COMPOSE_PROJECT_NAME} + depends_on: + django: + condition: service_healthy + environment: + - IS_CELERY=True + entrypoint: ["/usr/src/urban/entrypoint.sh"] + command: "celery-cmd" + + # Nginx is serving django static and media files and proxies to django and geonode + geonode: + image: ${COMPOSE_PROJECT_NAME}/nginx:${NGINX_BASE_IMAGE_VERSION} + build: + context: ./docker/nginx + dockerfile: Dockerfile + args: + - BASE_IMAGE_VERSION=${NGINX_BASE_IMAGE_VERSION} + container_name: nginx4${COMPOSE_PROJECT_NAME} + env_file: + - .env + environment: + - RESOLVER=127.0.0.11 + ports: + - "${HTTP_PORT}:80" + - "${HTTPS_PORT}:443" + volumes: + - nginx-confd:/etc/nginx + - nginx-certificates:/geonode-certificates + - statics:/mnt/volumes/statics + restart: unless-stopped + + # memcached service + memcached: + image: memcached:alpine + container_name: memcached4${COMPOSE_PROJECT_NAME} + command: memcached ${MEMCACHED_OPTIONS} + restart: on-failure + healthcheck: + test: nc -z 127.0.0.1 11211 + interval: 30s + timeout: 30s + retries: 5 + start_period: 30s + + # Gets and installs letsencrypt certificates + letsencrypt: + image: ${COMPOSE_PROJECT_NAME}/letsencrypt:${LETSENCRYPT_BASE_IMAGE_VERSION} + build: + context: ./docker/letsencrypt + dockerfile: Dockerfile + args: + - BASE_IMAGE_VERSION=${LETSENCRYPT_BASE_IMAGE_VERSION} + container_name: letsencrypt4${COMPOSE_PROJECT_NAME} + env_file: + - .env + volumes: + - nginx-certificates:/geonode-certificates + restart: unless-stopped + + # Geoserver backend + geoserver: + image: ${COMPOSE_PROJECT_NAME}/geoserver:${GEOSERVER_BASE_IMAGE_VERSION} + build: + context: ./docker/geoserver + dockerfile: Dockerfile + args: + - BASE_IMAGE_VERSION=${GEOSERVER_BASE_IMAGE_VERSION} + container_name: geoserver4${COMPOSE_PROJECT_NAME} + healthcheck: + test: "curl -m 10 --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://geoserver:8080/geoserver/ows" + start_period: 60s + interval: 60s + timeout: 10s + retries: 2 + env_file: + - .env + ports: + - "8080:8080" + volumes: + - statics:/mnt/volumes/statics + - geoserver-data-dir:/geoserver_data/data + - backup-restore:/backup_restore + - data:/data + - tmp:/tmp + restart: unless-stopped + depends_on: + data-dir-conf: + condition: service_healthy + django: + condition: service_healthy + + data-dir-conf: + image: ${COMPOSE_PROJECT_NAME}/geoserver_data:${GEOSERVER_DATA_BASE_IMAGE_VERSION} + build: + context: ./docker/geoserver_data + dockerfile: Dockerfile + args: + - BASE_IMAGE_VERSION=${GEOSERVER_DATA_BASE_IMAGE_VERSION} + container_name: gsconf4${COMPOSE_PROJECT_NAME} + entrypoint: sleep infinity + volumes: + - geoserver-data-dir:/geoserver_data/data + restart: unless-stopped + healthcheck: + test: "ls -A '/geoserver_data/data' | wc -l" + + # PostGIS database. + db: + image: ${COMPOSE_PROJECT_NAME}/postgis:${POSTGRES_BASE_IMAGE_VERSION} + build: + context: ./docker/postgresql + dockerfile: Dockerfile + args: + - BASE_IMAGE_VERSION=${POSTGRES_BASE_IMAGE_VERSION} + command: postgres -c "max_connections=${POSTGRESQL_MAX_CONNECTIONS}" + container_name: db4${COMPOSE_PROJECT_NAME} + env_file: + - .env + volumes: + - dbdata:/var/lib/postgresql/data + - dbbackups:/pg_backups + restart: unless-stopped + healthcheck: + test: "pg_isready -d postgres -U postgres" + # uncomment to enable remote connections to postgres + #ports: + # - "5432:5432" + + # Vanilla RabbitMQ service. This is needed by celery + rabbitmq: + image: rabbitmq:3-alpine + container_name: rabbitmq4${COMPOSE_PROJECT_NAME} + volumes: + - rabbitmq:/var/lib/rabbitmq + restart: unless-stopped + +volumes: + statics: + name: ${COMPOSE_PROJECT_NAME}-statics + nginx-confd: + name: ${COMPOSE_PROJECT_NAME}-nginxconfd + nginx-certificates: + name: ${COMPOSE_PROJECT_NAME}-nginxcerts + geoserver-data-dir: + name: ${COMPOSE_PROJECT_NAME}-gsdatadir + dbdata: + name: ${COMPOSE_PROJECT_NAME}-dbdata + dbbackups: + name: ${COMPOSE_PROJECT_NAME}-dbbackups + backup-restore: + name: ${COMPOSE_PROJECT_NAME}-backup-restore + data: + name: ${COMPOSE_PROJECT_NAME}-data + tmp: + name: ${COMPOSE_PROJECT_NAME}-tmp + rabbitmq: + name: ${COMPOSE_PROJECT_NAME}-rabbitmq diff --git a/docker-purge.sh b/docker-purge.sh new file mode 100755 index 0000000..f66271f --- /dev/null +++ b/docker-purge.sh @@ -0,0 +1,2 @@ +docker kill $(docker ps -q); docker rm $(docker ps -a -q); docker rmi $(docker images -q); docker volume ls -qf dangling=true | xargs -r docker volume rm; docker system prune -a +docker volume prune diff --git a/docker/geoserver/Dockerfile b/docker/geoserver/Dockerfile new file mode 100644 index 0000000..ea638a8 --- /dev/null +++ b/docker/geoserver/Dockerfile @@ -0,0 +1,2 @@ +ARG BASE_IMAGE_VERSION +FROM geonode/geoserver:${BASE_IMAGE_VERSION} \ No newline at end of file diff --git a/docker/geoserver_data/Dockerfile b/docker/geoserver_data/Dockerfile new file mode 100644 index 0000000..dced1d8 --- /dev/null +++ b/docker/geoserver_data/Dockerfile @@ -0,0 +1,2 @@ +ARG BASE_IMAGE_VERSION +FROM geonode/geoserver_data:${BASE_IMAGE_VERSION} \ No newline at end of file diff --git a/docker/letsencrypt/Dockerfile b/docker/letsencrypt/Dockerfile new file mode 100644 index 0000000..b8e6527 --- /dev/null +++ b/docker/letsencrypt/Dockerfile @@ -0,0 +1,2 @@ +ARG BASE_IMAGE_VERSION +FROM geonode/letsencrypt:${BASE_IMAGE_VERSION} \ No newline at end of file diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile new file mode 100644 index 0000000..193b269 --- /dev/null +++ b/docker/nginx/Dockerfile @@ -0,0 +1,2 @@ +ARG BASE_IMAGE_VERSION +FROM geonode/nginx:${BASE_IMAGE_VERSION} \ No newline at end of file diff --git a/docker/postgresql/Dockerfile b/docker/postgresql/Dockerfile new file mode 100644 index 0000000..1c7d2fb --- /dev/null +++ b/docker/postgresql/Dockerfile @@ -0,0 +1,2 @@ +ARG BASE_IMAGE_VERSION +FROM geonode/postgis:${BASE_IMAGE_VERSION} \ No newline at end of file diff --git a/generated-project-vagrant-compose.sh b/generated-project-vagrant-compose.sh new file mode 100644 index 0000000..f91f5f0 --- /dev/null +++ b/generated-project-vagrant-compose.sh @@ -0,0 +1,22 @@ + +#!/usr/bin/env bash +# this is the generated-project script to create an generated-project canary project. + +source /usr/share/virtualenvwrapper/virtualenvwrapper.sh +mkvirtualenv --python=/usr/bin/python3 generated-project +pip install Django==3.2.16 +rm -rf generated-project +django-admin startproject --template=/home/vagrant/geonode-project -e py,sh,md,rst,json,yml,ini,env,sample,properties -n monitoring-cron -n Dockerfile generated-project +cd /home/vagrant/generated-project +python create-envfile.py --geonodepwd geonode \ + --geoserverpwd geoserver \ + --pgpwd postgres \ + --dbpwd geonode \ + --geodbpwd geonode +sed -i 's/GEOSERVER_WEB_UI_LOCATION=http:\/\/localhost\/geoserver\//GEOSERVER_WEB_UI_LOCATION=http:\/\/localhost:8888\/geoserver\//' .env +sed -i 's/GEOSERVER_PUBLIC_LOCATION=http:\/\/localhost\/geoserver\//GEOSERVER_PUBLIC_LOCATION=http:\/\/localhost:8888\/geoserver\//' .env +sed -i 's/SITEURL=.*/SITEURL=http:\/\/localhost:8888\//' .env +sed -i 's/GEONODE_LB_PORT=80/GEONODE_LB_PORT=8888/' .env +chown -R vagrant:vagrant /home/vagrant/generated-project +sudo docker-compose build +sudo docker-compose up -d diff --git a/generated-project-vagrant-swarm.sh b/generated-project-vagrant-swarm.sh new file mode 100644 index 0000000..773370c --- /dev/null +++ b/generated-project-vagrant-swarm.sh @@ -0,0 +1,22 @@ + +#!/usr/bin/env bash +# this is the generated-project script to create an generated-project canary project. +sudo docker swarm init +source /usr/share/virtualenvwrapper/virtualenvwrapper.sh +mkvirtualenv --python=/usr/bin/python3 generated-project +pip install Django==3.2.16 +rm -rf generated-project +django-admin startproject --template=/home/vagrant/geonode-project -e py,sh,md,rst,json,yml,ini,env,sample,properties -n monitoring-cron -n Dockerfile generated-project +cd /home/vagrant/generated-project +python create-envfile.py --geonodepwd geonode \ + --geoserverpwd geoserver \ + --pgpwd postgres \ + --dbpwd geonode \ + --geodbpwd geonode +sed -i 's/GEOSERVER_WEB_UI_LOCATION=http:\/\/localhost\/geoserver\//GEOSERVER_WEB_UI_LOCATION=http:\/\/localhost:8888\/geoserver\//' .env +sed -i 's/GEOSERVER_PUBLIC_LOCATION=http:\/\/localhost\/geoserver\//GEOSERVER_PUBLIC_LOCATION=http:\/\/localhost:8888\/geoserver\//' .env +sed -i 's/SITEURL=.*/SITEURL=http:\/\/localhost:8888\//' .env +sed -i 's/GEONODE_LB_PORT=80/GEONODE_LB_PORT=8888/' .env +chown -R vagrant:vagrant /home/vagrant/generated-project +sudo docker-compose -f ./geonode-stack.yml build +sudo docker stack deploy -c ./geonode-stack.yml geonode-stack diff --git a/geonode-stack.yml b/geonode-stack.yml new file mode 100644 index 0000000..8acf18a --- /dev/null +++ b/geonode-stack.yml @@ -0,0 +1,165 @@ +version: '3.9' + +# Common Django template for GeoNode and Celery services below +x-common-django: + &default-common-django + image: django:latest + env_file: + - .env + volumes: + # - '.:/usr/src/urban' + - statics:/mnt/volumes/statics + - geoserver-data-dir:/geoserver_data/data + - backup-restore:/backup_restore + - data:/data + - tmp:/tmp + +# Common template for service restart policy +x-common-swarm-deploy: + &default-common-swarm-deploy + replicas: 1 + restart_policy: + condition: any + delay: 0s + max_attempts: 10 + window: 60s + +services: + + # Our custom django application. It includes Geonode. + django: + << : *default-common-django + deploy: *default-common-swarm-deploy + build: + context: ./ + dockerfile: Dockerfile + healthcheck: + test: "curl --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://127.0.0.1:8000/" + interval: 60s + timeout: 10s + retries: 1 + start_period: 60s + environment: + IS_CELERY: 'False' + entrypoint: ["/usr/src/urban/entrypoint.sh"] + command: "uwsgi --ini /usr/src/urban/uwsgi.ini" + + # Celery worker that executes celery tasks created by Django. + celery: + << : *default-common-django + deploy: *default-common-swarm-deploy + depends_on: + - django + environment: + - IS_CELERY=True + entrypoint: ["/usr/src/urban/entrypoint.sh"] + command: "celery-cmd" + + # Nginx is serving django static and media files and proxies to django and geonode + geonode: + deploy: *default-common-swarm-deploy + image: geonode/nginx:3.x + build: ./docker/nginx/ + environment: + - HTTPS_HOST=localhost + - HTTP_HOST=localhost + - HTTPS_PORT=443 + - HTTP_PORT=80 + - LETSENCRYPT_MODE=disabled + - RESOLVER=127.0.0.11 + env_file: + - .env + ports: + - "${HTTP_PORT}:80" + - "${HTTPS_PORT}:443" + volumes: + - nginx-confd:/etc/nginx + - nginx-certificates:/geonode-certificates + - statics:/mnt/volumes/statics + + # Gets and installs letsencrypt certificates + letsencrypt: + deploy: *default-common-swarm-deploy + image: geonode/letsencrypt:latest + build: ./docker/letsencrypt/ + environment: + - HTTPS_HOST=localhost + - HTTP_HOST=localhost + - ADMIN_EMAIL=admin@localhost + - LETSENCRYPT_MODE=disabled + env_file: + - .env + volumes: + - nginx-certificates:/geonode-certificates + + # Geoserver backend + geoserver: + deploy: *default-common-swarm-deploy + image: geonode/geoserver:2.20.6 + build: ./docker/geoserver/ + env_file: + - .env + healthcheck: + test: curl --fail -s http://localhost:8080/geoserver/rest/workspaces/geonode.html || exit 1 + interval: 60s + timeout: 10s + retries: 1 + start_period: 60s + volumes: + - statics:/mnt/volumes/statics + - geoserver-data-dir:/geoserver_data/data + - backup-restore:/backup_restore + - data:/data + - tmp:/tmp + + data-dir-conf: + image: geonode/geoserver_data:2.20.6 + command: /bin/true + volumes: + - geoserver-data-dir:/geoserver_data/data + + # PostGIS database. + db: + deploy: *default-common-swarm-deploy + # use geonode official postgis 13 image + image: geonode/postgis:13 + command: postgres -c "max_connections=${POSTGRESQL_MAX_CONNECTIONS}" + volumes: + - dbdata:/var/lib/postgresql/data + - dbbackups:/pg_backups + env_file: + - .env + # uncomment to enable remote connections to postgres + #ports: + # - "5432:5432" + + # Vanilla RabbitMQ service. This is needed by celery + rabbitmq: + deploy: *default-common-swarm-deploy + image: rabbitmq:3.7-alpine + volumes: + - rabbitmq:/var/lib/rabbitmq + env_file: + - .env + +volumes: + statics: + name: statics + nginx-confd: + name: nginxconfd + nginx-certificates: + name: nginxcerts + geoserver-data-dir: + name: gsdatadir + dbdata: + name: dbdata + dbbackups: + name: dbbackups + backup-restore: + name: backup-restore + data: + name: data + tmp: + name: tmp + rabbitmq: + name: rabbitmq diff --git a/playbook.yml b/playbook.yml new file mode 100644 index 0000000..7af3349 --- /dev/null +++ b/playbook.yml @@ -0,0 +1,46 @@ +- name: Provision a GeoNode into Production + hosts: production + remote_user: ubuntu + vars: + app_name: urban + github_user: geonode + server_name: 0.0.0.0 + deploy_user: ubuntu + code_repository: https://github.com/-----/urban.git" # e.g., "https://github.com/GeoNode/urban.git" + branch_name: master + virtualenv_dir: "/home/ubuntu/.venvs" + site_url: "http://localhost:8000/" # The public url of the GeoNode instance + geoserver_url: "https://artifacts.geonode.org/geoserver/2.20.6/geoserver.war" # geoserver_url should match what is found in dev_config.yml + pg_max_connections: 100 + pg_shared_buffers: 128MB + tomcat_xms: "1024M" + tomcat_xmx: "2048M" + nginx_client_max_body_size: "400M" + letsencrypt: False + gather_facts: False + pre_tasks: + - name: Install python for Ansible + become: yes + become_user: root + raw: test -e /usr/bin/python || (apt -y update && apt install -y python3-minimal) + - name: 'Reconfigue Locales' + become: yes + become_user: root + shell: "" + with_items: + - "export LANGUAGE=en_US.UTF-8" + - "export LANG=en_US.UTF-8" + - "export LC_ALL=en_US.UTF-8" + - "locale-gen --purge en_US.UTF-8" + - "echo 'LANG=en_US.UTF-8\nLANGUAGE=en_US:en\n' > /etc/default/locale" + - name: "Install cul, vim, and unzip" + become: yes + become_user: root + apt: name="" state=latest + with_items: + - curl + - vim + - unzip + - setup: # aka gather_facts + roles: + - { role: GeoNode.geonode } diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..bc22e6d --- /dev/null +++ b/src/Makefile @@ -0,0 +1,29 @@ +up: + # bring up the services + docker-compose up -d + +build: + docker-compose build django + docker-compose build celery + +sync: + # set up the database tablea + docker-compose run django python manage.py makemigrations --noinput + docker-compose exec django python manage.py migrate account --noinput + docker-compose run django python manage.py migrate --noinput + +wait: + sleep 5 + +logs: + docker-compose logs --follow + +down: + docker-compose down + +test: + docker-compose run django python manage.py test --failfast + +reset: down up wait sync + +hardreset: pull build reset diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..9457ab7 --- /dev/null +++ b/src/README.md @@ -0,0 +1 @@ +# Urban diff --git a/src/celery-cmd b/src/celery-cmd new file mode 100755 index 0000000..a600163 --- /dev/null +++ b/src/celery-cmd @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# A configurable celery command. +# Luca Pasquali +CELERY_BIN=${CELERY_BIN:-"$(which celery||echo celery)"} +CELERY_APP=${CELERY_APP:-"geonode.celery_app:app"} +CELERY__STATE_DB=${CELERY__STATE_DB:-"/mnt/volumes/statics/worker@%h.state"} +# expressed in KB +CELERY__MAX_MEMORY_PER_CHILD=${CELERY__MAX_MEMORY_PER_CHILD:-"200000"} +CELERY__AUTOSCALE_VALUES=${CELERY__AUTOSCALE_VALUES:-"15,10"} +CELERY__MAX_TASKS_PER_CHILD=${CELERY__MAX_TASKS_PER_CHILD:-"10"} +CELERY__OPTS=${CELERY__OPTS:-"--without-gossip --without-mingle -Ofair -B -E"} +CELERY__BEAT_SCHEDULE=${CELERY__BEAT_SCHEDULE:-"celery.beat:PersistentScheduler"} +CELERY__LOG_LEVEL=${CELERY__LOG_LEVEL:-"INFO"} +CELERY__LOG_FILE=${CELERY__LOG_FILE:-"/var/log/celery.log"} +CELERY__WORKER_NAME=${CELERY__WORKER_NAME:-"worker1@%h"} +CELERY__WORKER_CONCURRENCY=${CELERY__WORKER_CONCURRENCY:-"4"} + +$CELERY_BIN -A $CELERY_APP worker --autoscale=$CELERY__AUTOSCALE_VALUES \ + --max-memory-per-child=$CELERY__MAX_MEMORY_PER_CHILD $CELERY__OPTS \ + --statedb=$CELERY__STATE_DB --scheduler=$CELERY__BEAT_SCHEDULE \ + --loglevel=$CELERY__LOG_LEVEL -n $CELERY__WORKER_NAME -f $CELERY__LOG_FILE \ + --concurrency=$CELERY__WORKER_CONCURRENCY --max-tasks-per-child=$CELERY__MAX_TASKS_PER_CHILD diff --git a/src/celery.sh b/src/celery.sh new file mode 100644 index 0000000..f574656 --- /dev/null +++ b/src/celery.sh @@ -0,0 +1,4 @@ +#!/bin/bash +nohup celery -A geonode.celery_app:app beat -l DEBUG -f /var/log/celery.log &>/dev/null & +nohup celery -A geonode.celery_app:app worker --without-gossip --without-mingle -Ofair -B -E --statedb=worker.state --scheduler=celery.beat:PersistentScheduler --loglevel=INFO --concurrency=2 -n worker1@%h -f /var/log/celery.log &>/dev/null & +nohup celery -A geonode.celery_app:app flower --auto_refresh=True --debug=False --broker=${BROKER_URL} --basic_auth=${ADMIN_USERNAME}:${ADMIN_PASSWORD} --address=0.0.0.0 --port=5555 &>/dev/null & \ No newline at end of file diff --git a/src/dev_config.yml b/src/dev_config.yml new file mode 100644 index 0000000..0f7ec76 --- /dev/null +++ b/src/dev_config.yml @@ -0,0 +1,8 @@ +--- +GEOSERVER_URL: "https://artifacts.geonode.org/geoserver/2.24.2/geoserver.war" +DATA_DIR_URL: "https://artifacts.geonode.org/geoserver/2.24.2/geonode-geoserver-ext-web-app-data.zip" +JETTY_RUNNER_URL: "https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-runner/9.4.31.v20200723/jetty-runner-9.4.31.v20200723.jar" +WINDOWS: + py2exe: "http://downloads.sourceforge.net/project/py2exe/py2exe/0.6.9/py2exe-0.6.9.win32-py2.7.exe" + pyproj: "https://pyproj.googlecode.com/files/pyproj-1.9.3.win32-py2.7.exe" + lxml: "https://pypi.python.org/packages/2.7/l/lxml/lxml-3.6.0.win32-py2.7.exe" diff --git a/src/entrypoint.sh b/src/entrypoint.sh new file mode 100755 index 0000000..fd5619d --- /dev/null +++ b/src/entrypoint.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +# Exit script in case of error +set -e + +INVOKE_LOG_STDOUT=${INVOKE_LOG_STDOUT:-FALSE} +invoke () { + if [ $INVOKE_LOG_STDOUT = 'true' ] || [ $INVOKE_LOG_STDOUT = 'True' ] + then + /usr/local/bin/invoke $@ + else + /usr/local/bin/invoke $@ > /usr/src/urban/invoke.log 2>&1 + fi + echo "$@ tasks done" +} + +# Start cron service +service cron restart + +echo $"\n\n\n" +echo "-----------------------------------------------------" +echo "STARTING DJANGO ENTRYPOINT $(date)" +echo "-----------------------------------------------------" + +invoke update + +source $HOME/.bashrc +source $HOME/.override_env + +echo DOCKER_API_VERSION=$DOCKER_API_VERSION +echo POSTGRES_USER=$POSTGRES_USER +echo POSTGRES_PASSWORD=$POSTGRES_PASSWORD +echo DATABASE_URL=$DATABASE_URL +echo GEODATABASE_URL=$GEODATABASE_URL +echo SITEURL=$SITEURL +echo ALLOWED_HOSTS=$ALLOWED_HOSTS +echo GEOSERVER_PUBLIC_LOCATION=$GEOSERVER_PUBLIC_LOCATION +echo MONITORING_ENABLED=$MONITORING_ENABLED +echo MONITORING_HOST_NAME=$MONITORING_HOST_NAME +echo MONITORING_SERVICE_NAME=$MONITORING_SERVICE_NAME +echo MONITORING_DATA_TTL=$MONITORING_DATA_TTL + +# invoke waitfordbs + +cmd="$@" + +if [ ${IS_CELERY} = "true" ] || [ ${IS_CELERY} = "True" ] +then + echo "Executing Celery server $cmd for Production" +else + + invoke migrations + invoke prepare + + if [ ${FORCE_REINIT} = "true" ] || [ ${FORCE_REINIT} = "True" ] || [ ! -e "/mnt/volumes/statics/geonode_init.lock" ]; then + invoke fixtures + invoke monitoringfixture + invoke initialized + invoke updateadmin + fi + + invoke statics + + echo "Executing UWSGI server $cmd for Production" +fi + +echo "-----------------------------------------------------" +echo "FINISHED DJANGO ENTRYPOINT --------------------------" +echo "-----------------------------------------------------" + +# Run the CMD +echo "got command $cmd" +exec $cmd diff --git a/src/fixtures/apikey_docker.json b/src/fixtures/apikey_docker.json new file mode 100644 index 0000000..1c98b66 --- /dev/null +++ b/src/fixtures/apikey_docker.json @@ -0,0 +1,11 @@ +[ + { + "fields": { + "user": 1000, + "key": "pyxW5djJ7XsjeFUXduAsGpR4xMGUwpeBGQRqTeT3", + "created": "2018-06-28T14:54:51Z" + }, + "model": "tastypie.apikey", + "pk": 1 + } +] \ No newline at end of file diff --git a/src/fixtures/default_oauth_apps.json b/src/fixtures/default_oauth_apps.json new file mode 100644 index 0000000..41b8bd9 --- /dev/null +++ b/src/fixtures/default_oauth_apps.json @@ -0,0 +1,21 @@ +[ +{ + "model": "oauth2_provider.application", + "pk": 1001, + "fields": { + "skip_authorization": true, + "created": "2018-05-31T10:00:31.661Z", + "updated": "2018-05-31T11:30:31.245Z", + "algorithm": "RS256", + "redirect_uris": "http://localhost:8080/geoserver/index.html\nhttp://localhost/geoserver/index.html", + "name": "GeoServer", + "authorization_grant_type": "authorization-code", + "client_type": "confidential", + "client_id": "Jrchz2oPY3akmzndmgUTYrs9gczlgoV20YPSvqaV", + "client_secret": "rCnp5txobUo83EpQEblM8fVj3QT5zb5qRfxNsuPzCqZaiRyIoxM4jdgMiZKFfePBHYXCLd7B8NlkfDBY9HKeIQPcy5Cp08KQNpRHQbjpLItDHv12GvkSeXp6OxaUETv3", + "user": [ + "admin" + ] + } +} +] diff --git a/src/fixtures/default_oauth_apps_docker.json b/src/fixtures/default_oauth_apps_docker.json new file mode 100644 index 0000000..975680f --- /dev/null +++ b/src/fixtures/default_oauth_apps_docker.json @@ -0,0 +1,21 @@ +[ +{ + "model": "oauth2_provider.application", + "pk": 1001, + "fields": { + "skip_authorization": true, + "created": "2018-05-31T10:00:31.661Z", + "updated": "2018-05-31T11:30:31.245Z", + "algorithm": "RS256", + "redirect_uris": "http://geonode/geoserver", + "name": "GeoServer", + "authorization_grant_type": "authorization-code", + "client_type": "confidential", + "client_id": "Jrchz2oPY3akmzndmgUTYrs9gczlgoV20YPSvqaV", + "client_secret": "rCnp5txobUo83EpQEblM8fVj3QT5zb5qRfxNsuPzCqZaiRyIoxM4jdgMiZKFfePBHYXCLd7B8NlkfDBY9HKeIQPcy5Cp08KQNpRHQbjpLItDHv12GvkSeXp6OxaUETv3", + "user": [ + "admin" + ] + } +} +] diff --git a/src/fixtures/django_celery_beat.json b/src/fixtures/django_celery_beat.json new file mode 100644 index 0000000..66c2b6a --- /dev/null +++ b/src/fixtures/django_celery_beat.json @@ -0,0 +1,72 @@ +[ + { + "model": "django_celery_beat.intervalschedule", + "pk": 1, + "fields": { + "every": 60, + "period": "seconds" + } + }, + { + "model": "django_celery_beat.crontabschedule", + "pk": 1, + "fields": { + "minute": "0", + "hour": "4", + "day_of_week": "*", + "day_of_month": "*", + "month_of_year": "*" + } + }, + { + "model": "django_celery_beat.periodictasks", + "pk": 1, + "fields": { + "last_update": "2019-10-14T12:56:49.352Z" + } + }, + { + "model": "django_celery_beat.periodictask", + "pk": 1, + "fields": { + "name": "celery.backend_cleanup", + "task": "celery.backend_cleanup", + "interval": null, + "crontab": 1, + "solar": null, + "args": "[]", + "kwargs": "{}", + "queue": null, + "exchange": null, + "routing_key": null, + "expires": null, + "enabled": true, + "last_run_at": null, + "total_run_count": 0, + "date_changed": "2019-10-14T12:50:54.847Z", + "description": "" + } + }, + { + "model": "django_celery_beat.periodictask", + "pk": 2, + "fields": { + "name": "delayed-security-sync-task", + "task": "geonode.security.tasks.synch_guardian", + "interval": 1, + "crontab": null, + "solar": null, + "args": "[]", + "kwargs": "{}", + "queue": null, + "exchange": null, + "routing_key": null, + "expires": null, + "enabled": true, + "last_run_at": null, + "total_run_count": 0, + "date_changed": "2019-10-14T12:56:37.554Z", + "description": "" + } + } +] \ No newline at end of file diff --git a/src/fixtures/initial_data.json b/src/fixtures/initial_data.json new file mode 100644 index 0000000..f025e79 --- /dev/null +++ b/src/fixtures/initial_data.json @@ -0,0 +1,4877 @@ +[ + { + "pk": 1, + "model": "base.topiccategory", + "fields": { + "description": "Information pertaining to earth sciences. Examples: geophysical features and processes, geology, minerals, sciences dealing with the composition, structure and origin of the earth s rocks, risks of earthquakes, volcanic activity, landslides, gravity information, soils, permafrost, hydrogeology, erosion.", + "gn_description": "Geoscientific Information", + "is_choice": true, + "fa_class": "fa-bullseye", + "identifier": "geoscientificInformation" + } + }, + { + "pk": 2, + "model": "base.topiccategory", + "fields": { + "description": "Rearing of animals and/or cultivation of plants. Examples: agriculture, irrigation, aquaculture, plantations, herding, pests and diseases affecting crops and livestock.", + "gn_description": "Farming", + "is_choice": true, + "fa_class": "fa-lemon-o", + "identifier": "farming" + } + }, + { + "pk": 3, + "model": "base.topiccategory", + "fields": { + "description": "Height above or below sea level. Examples: altitude, bathymetry, digital elevation models, slope, derived products.", + "gn_description": "Elevation", + "is_choice": true, + "fa_class": "fa-flag", + "identifier": "elevation" + } + }, + { + "pk": 4, + "model": "base.topiccategory", + "fields": { + "description": "Energy, water and waste systems and communications infrastructure and services. Examples: hydroelectricity, geothermal, solar and nuclear sources of energy, water purification and distribution, sewage collection and disposal, electricity and gas distribution, data communication, telecommunication, radio, communication networks.", + "gn_description": "Utilities Communication", + "is_choice": true, + "fa_class": "fa-phone", + "identifier": "utilitiesCommunication" + } + }, + { + "pk": 5, + "model": "base.topiccategory", + "fields": { + "description": "Features and characteristics of salt water bodies (excluding inland waters). Examples: tides, tidal waves, coastal information, reefs.", + "gn_description": "Oceans", + "is_choice": true, + "fa_class": "fa-anchor", + "identifier": "oceans" + } + }, + { + "pk": 6, + "model": "base.topiccategory", + "fields": { + "description": "Legal land descriptions. Examples: political and administrative boundaries.", + "gn_description": "Boundaries", + "is_choice": true, + "fa_class": "fa-ellipsis-h", + "identifier": "boundaries" + } + }, + { + "pk": 7, + "model": "base.topiccategory", + "fields": { + "description": "Inland water features, drainage systems and their characteristics. Examples: rivers and glaciers, salt lakes, water utilization plans, dams, currents, floods, water quality, hydrographic charts.", + "gn_description": "Inland Waters", + "is_choice": true, + "fa_class": "fa-tint", + "identifier": "inlandWaters" + } + }, + { + "pk": 8, + "model": "base.topiccategory", + "fields": { + "description": "Military bases, structures, activities. Examples: barracks, training grounds, military transportation, information collection.", + "gn_description": "Intelligence Military", + "is_choice": true, + "fa_class": "fa-fighter-jet", + "identifier": "intelligenceMilitary" + } + }, + { + "pk": 9, + "model": "base.topiccategory", + "fields": { + "description": "Environmental resources, protection and conservation. Examples: environmental pollution, waste storage and treatment, environmental impact assessment, monitoring environmental risk, nature reserves, landscape.", + "gn_description": "Environment", + "is_choice": true, + "fa_class": "fa-tree", + "identifier": "environment" + } + }, + { + "pk": 10, + "model": "base.topiccategory", + "fields": { + "description": "Positional information and services. Examples: addresses, geodetic networks, control points, postal zones and services, place names.", + "gn_description": "Location", + "is_choice": true, + "fa_class": "fa-map-marker", + "identifier": "location" + } + }, + { + "pk": 11, + "model": "base.topiccategory", + "fields": { + "description": "Economic activities, conditions and employment. Examples: production, labour, revenue, commerce, industry, tourism and ecotourism, forestry, fisheries, commercial or subsistence hunting, exploration and exploitation of resources such as minerals, oil and gas.", + "gn_description": "Economy", + "is_choice": true, + "fa_class": "fa-shopping-cart", + "identifier": "economy" + } + }, + { + "pk": 12, + "model": "base.topiccategory", + "fields": { + "description": "Information used for appropriate actions for future use of the land. Examples: land use maps, zoning maps, cadastral surveys, land ownership.", + "gn_description": "Planning Cadastre", + "is_choice": true, + "fa_class": "fa-home", + "identifier": "planningCadastre" + } + }, + { + "pk": 13, + "model": "base.topiccategory", + "fields": { + "description": "Flora and/or fauna in natural environment. Examples: wildlife, vegetation, biological sciences, ecology, wilderness, sealife, wetlands, habitat.", + "gn_description": "Biota", + "is_choice": true, + "fa_class": "fa-leaf", + "identifier": "biota" + } + }, + { + "pk": 14, + "model": "base.topiccategory", + "fields": { + "description": "Health, health services, human ecology, and safety. Examples: disease and illness, factors affecting health, hygiene, substance abuse, mental and physical health, health services.", + "gn_description": "Health", + "is_choice": true, + "fa_class": "fa-stethoscope", + "identifier": "health" + } + }, + { + "pk": 15, + "model": "base.topiccategory", + "fields": { + "description": "Base maps. Examples: land cover, topographic maps, imagery, unclassified images, annotations.", + "gn_description": "Imagery Base Maps Earth Cover", + "is_choice": true, + "fa_class": "fa-globe", + "identifier": "imageryBaseMapsEarthCover" + } + }, + { + "pk": 16, + "model": "base.topiccategory", + "fields": { + "description": "Means and aids for conveying persons and/or goods. Examples: roads, airports/airstrips, shipping routes, tunnels, nautical charts, vehicle or vessel location, aeronautical charts, railways.", + "gn_description": "Transportation", + "is_choice": true, + "fa_class": "fa-truck", + "identifier": "transportation" + } + }, + { + "pk": 17, + "model": "base.topiccategory", + "fields": { + "description": "Characteristics of society and cultures. Examples: settlements, anthropology, archaeology, education, traditional beliefs, manners and customs, demographic data, recreational areas and activities, social impact assessments, crime and justice, census information.", + "gn_description": "Society", + "is_choice": true, + "fa_class": "fa-comments", + "identifier": "society" + } + }, + { + "pk": 18, + "model": "base.topiccategory", + "fields": { + "description": "Man-made construction. Examples: buildings, museums, churches, factories, housing, monuments, shops, towers.", + "gn_description": "Structure", + "is_choice": true, + "fa_class": "fa-building", + "identifier": "structure" + } + }, + { + "pk": 19, + "model": "base.topiccategory", + "fields": { + "description": "Processes and phenomena of the atmosphere. Examples: cloud cover, weather, climate, atmospheric conditions, climate change, precipitation.", + "gn_description": "Climatology Meteorology Atmosphere", + "is_choice": true, + "fa_class": "fa-cloud", + "identifier": "climatologyMeteorologyAtmosphere" + } + }, + { + "pk": 20, + "model": "base.topiccategory", + "fields": { + "description": "Settlements, anthropology, archaeology, education, traditional beliefs, manners and customs, demographic data, recreational areas and activities, social impact assessments, crime and justice, census information. Economic activities, conditions and employment.", + "gn_description": "Population", + "is_choice": true, + "fa_class": "fa-male", + "identifier": "population" + } + }, + { + "pk": 1, + "model": "base.spatialrepresentationtype", + "fields": { + "is_choice": true, + "gn_description": "vector data is used to represent geographic data", + "identifier": "vector", + "description": "vector data is used to represent geographic data" + } + }, + { + "pk": 2, + "model": "base.spatialrepresentationtype", + "fields": { + "is_choice": true, + "gn_description": "grid data is used to represent geographic data", + "identifier": "grid", + "description": "grid data is used to represent geographic data" + } + }, + { + "pk": 3, + "model": "base.spatialrepresentationtype", + "fields": { + "is_choice": true, + "gn_description": "textual or tabular data is used to represent geographic data", + "identifier": "textTable", + "description": "textual or tabular data is used to represent geographic data" + } + }, + { + "pk": 4, + "model": "base.spatialrepresentationtype", + "fields": { + "is_choice": true, + "gn_description": "triangulated irregular network", + "identifier": "tin", + "description": "triangulated irregular network" + } + }, + { + "pk": 5, + "model": "base.spatialrepresentationtype", + "fields": { + "is_choice": true, + "gn_description": "three-dimensional view formed by the intersecting homologous rays of an overlapping pair of images", + "identifier": "stereoModel", + "description": "three-dimensional view formed by the intersecting homologous rays of an overlapping pair of images" + } + }, + { + "pk": 6, + "model": "base.spatialrepresentationtype", + "fields": { + "is_choice": true, + "gn_description": "scene from a video recording", + "identifier": "video", + "description": "scene from a video recording" + } + }, + { + "pk": 1, + "model": "base.license", + "fields": { + "identifier": "not_specified", + "name":"Not Specified", + "abbreviation":"", + "description":"The original author did not specify a license.", + "url":"", + "license_text":"Not applicable" + } + }, +{ + "pk": 2, + "model": "base.license", + "fields": { + "identifier":"varied_original", + "name":"Varied / Original", + "abbreviation":"", + "description":"This item is either licensed under multiple licenses. See the item's abstract for more information or contact the distributor.", + "url":"", + "license_text":"Not applicable" + } + }, + { + "pk": 3, + "model": "base.license", + "fields": { + "identifier":"varied_derived", + "name":"Varied / Derived", + "abbreviation":"", + "description":"The constituent parts of this item have different licenses. Go to each part to see license information.", + "url":"", + "license_text":"Not applicable" + } + }, + { + "pk": 4, + "model": "base.license", + "fields": { + "identifier":"public_domain", + "name":"Public Domain", + "abbreviation":"PD", + "description":"Works in the public domain may be used freely without the permission of the former copyright owner.", + "url":"http://www.copyright.gov/help/faq/faq-definitions.html", + "license_text":"The public domain is not a place. A work of authorship is in the “public domain” if it is no longer under copyright protection or if it failed to meet the requirements for copyright protection. Works in the public domain may be used freely without the permission of the former copyright owner." + } + }, + { + "pk": 5, + "model": "base.license", + "fields": { + "identifier":"public_domain_usg", + "name":"Public Domain / USG", + "abbreviation":"PD/USG", + "description":"This project constitutes a work of the United States Government and is not subject to domestic copyright protection under 17 USC § 105.", + "url":"https://raw.githubusercontent.com/state-hiu/cybergis-licenses/master/licenses/PUBLICDOMAIN-LICENSE-RAW.txt", + "license_text":"This project constitutes a work of the United States Government and is not subject to domestic copyright protection under 17 USC § 105." + } + }, + { + "pk": 6, + "model": "base.license", + "fields": { + "identifier":"odbl", + "name":"Open Data Commons Open Database License / OSM", + "abbreviation":"ODbL/OSM", + "description":"You are free to copy, distribute, transmit and adapt our data, as long as you credit OpenStreetMap and its contributors\nIf you alter or build upon our data, you may distribute the result only under the same licence.", + "url":"http://www.openstreetmap.org/copyright", + "license_text":"" + } + }, + { + "pk": 7, + "model": "base.license", + "fields": { + "identifier":"nextview", + "name":"NextView", + "abbreviation":"NV", + "description":"This data is licensed for use by the US Government (USG) under the NextView (NV) license and copyrighted by Digital Globe or GeoEye. The NV license allows the USG to share the imagery and Literal Imagery Derived Products (LIDP) with entities outside the USG when that entity is working directly with the USG, for the USG, or in a manner that is directly beneficial to the USG. The party receiving the data can only use the imagery or LIDP for the original purpose or only as otherwise agreed to by the USG. The party receiving the data cannot share the imagery or LIDP with a third party without express permission from the USG. At no time should this imagery or LIDP be used for other than USG-related purposes and must not be used for commercial gain. The copyright information should be maintained at all times. Your acceptance of these license terms is implied by your use.\nIn other words, you may only use NextView imagery linked from this site for digitizing OpenStreetMap data for humanitarian purposes.", + "url":"https://raw.githubusercontent.com/state-hiu/cybergis-licenses/master/licenses/NEXTVIEW-LICENSE-RAW.txt", + "license_text":"" + } + }, + { + "pk": 1, + "model": "base.restrictioncodetype", + "fields": { + "is_choice": true, + "gn_description": "exclusive right to the publication, production, or sale of the rights to a literary, dramatic, musical, or artistic work, or to the use of a commercial print or label, granted by law for a specified period of time to an author, composer, artist, distributor", + "identifier": "copyright", + "description": "exclusive right to the publication, production, or sale of the rights to a literary, dramatic, musical, or artistic work, or to the use of a commercial print or label, granted by law for a specified period of time to an author, composer, artist, distributor" + } + }, + { + "pk": 6, + "model": "base.restrictioncodetype", + "fields": { + "is_choice": true, + "gn_description": "rights to financial benefit from and control of distribution of non-tangible property that is a result of creativity", + "identifier": "intellectualPropertyRights", + "description": "rights to financial benefit from and control of distribution of non-tangible property that is a result of creativity" + } + }, + { + "pk": 5, + "model": "base.restrictioncodetype", + "fields": { + "is_choice": true, + "gn_description": "formal permission to do something", + "identifier": "license", + "description": "formal permission to do something" + } + }, + { + "pk": 8, + "model": "base.restrictioncodetype", + "fields": { + "is_choice": true, + "gn_description": "otherRestrictions", + "identifier": "limitation not listed", + "description": "otherRestrictions" + } + }, + { + "pk": 2, + "model": "base.restrictioncodetype", + "fields": { + "is_choice": true, + "gn_description": "government has granted exclusive right to make, sell, use or license an invention or discovery", + "identifier": "patent", + "description": "government has granted exclusive right to make, sell, use or license an invention or discovery" + } + }, + { + "pk": 3, + "model": "base.restrictioncodetype", + "fields": { + "is_choice": true, + "gn_description": "produced or sold information awaiting a patent", + "identifier": "patentPending", + "description": "produced or sold information awaiting a patent" + } + }, + { + "pk": 7, + "model": "base.restrictioncodetype", + "fields": { + "is_choice": true, + "gn_description": "withheld from general circulation or disclosure", + "identifier": "restricted", + "description": "withheld from general circulation or disclosure" + } + }, + { + "pk": 4, + "model": "base.restrictioncodetype", + "fields": { + "is_choice": true, + "gn_description": "a name, symbol, or other device identifying a product, officially registered and legally restricted to the use of the owner or manufacturer", + "identifier": "trademark", + "description": "a name, symbol, or other device identifying a product, officially registered and legally restricted to the use of the owner or manufacturer" + } + }, + { + "pk": 1, + "model": "base.region", + "fields": { + "rght": 516, + "code": "GLO", + "name": "Global", + "parent": null, + "level": 0, + "lft": 1, + "tree_id": 90, + "bbox_x0": -180, + "bbox_x1": 180, + "bbox_y0": -90, + "bbox_y1": 90 + } + }, + { + "pk": 2, + "model": "base.region", + "fields": { + "rght": 212, + "code": "NAM", + "name": "North America", + "parent": 254, + "level": 2, + "lft": 203, + "tree_id": 90, + "bbox_x0": -167.276413, + "bbox_x1": -52.23304, + "bbox_y0": 5.49955, + "bbox_y1": 83.162102 + } + }, + { + "pk": 3, + "model": "base.region", + "fields": { + "rght": 202, + "code": "CAM", + "name": "Central America", + "parent": 254, + "level": 2, + "lft": 187, + "tree_id": 90, + "bbox_x0": -118.867172, + "bbox_x1": -66.869827, + "bbox_y0": -4.23048, + "bbox_y1": 32.71862 + } + }, + { + "pk": 4, + "model": "base.region", + "fields": { + "rght": 242, + "code": "SAM", + "name": "South America", + "parent": 254, + "level": 2, + "lft": 213, + "tree_id": 90, + "bbox_x0": -109.47493, + "bbox_x1": -26.33247, + "bbox_y0": -59.450451, + "bbox_y1": 13.39029 + } + }, + { + "pk": 5, + "model": "base.region", + "fields": { + "rght": 433, + "code": "EUR", + "name": "Europe", + "parent": null, + "level": 1, + "lft": 318, + "tree_id": 90, + "bbox_x0": -31.266001, + "bbox_x1": 39.869301, + "bbox_y0": 27.636311, + "bbox_y1": 81.008797 + } + }, + { + "pk": 6, + "model": "base.region", + "fields": { + "rght": 317, + "code": "ASI", + "name": "Asia", + "parent": null, + "level": 1, + "lft": 246, + "tree_id": 90, + "bbox_x0": 19.6381, + "bbox_x1": 180, + "bbox_y0": -12.56111, + "bbox_y1": 82.50045 + } + }, + { + "pk": 7, + "model": "base.region", + "fields": { + "rght": 316, + "code": "SEA", + "name": "Southeast Asia", + "parent": 6, + "level": 2, + "lft": 293, + "tree_id": 90, + "bbox_x0": 68.0327, + "bbox_x1": 141.021805, + "bbox_y0": -12.56111, + "bbox_y1": 35.504211 + } + }, + { + "pk": 8, + "model": "base.region", + "fields": { + "rght": 260, + "code": "CTA", + "name": "Central Asia", + "parent": 6, + "level": 2, + "lft": 247, + "tree_id": 90, + "bbox_x0": 44.236641, + "bbox_x1": 90.076767, + "bbox_y0": 33.890511, + "bbox_y1": 54.845139 + } + }, + { + "pk": 9, + "model": "base.region", + "fields": { + "rght": 292, + "code": "SAS", + "name": "South Asia", + "parent": 6, + "level": 2, + "lft": 277, + "tree_id": 90, + "bbox_x0": 19.6381, + "bbox_x1": 180, + "bbox_y0": -12.56111, + "bbox_y1": 82.50045 + } + }, + { + "pk": 10, + "model": "base.region", + "fields": { + "rght": 127, + "code": "AFR", + "name": "Africa", + "parent": null, + "level": 1, + "lft": 2, + "tree_id": 90, + "bbox_x0": -25.35874, + "bbox_x1": 63.525379, + "bbox_y0": -46.900452, + "bbox_y1": 37.56712 + } + }, + { + "pk": 11, + "model": "base.region", + "fields": { + "rght": 64, + "code": "NAF", + "name": "North Africa", + "parent": 10, + "level": 2, + "lft": 49, + "tree_id": 90, + "bbox_x0": -17.10317, + "bbox_x1": 38.833801, + "bbox_y0": 3.48639, + "bbox_y1": 37.56712 + } + }, + { + "pk": 12, + "model": "base.region", + "fields": { + "rght": 48, + "code": "EAF", + "name": "East Africa", + "parent": 10, + "level": 2, + "lft": 13, + "tree_id": 90, + "bbox_x0": 22.855089, + "bbox_x1": 63.94656, + "bbox_y0": -25.84763, + "bbox_y1": 17.467039 + } + }, + { + "pk": 13, + "model": "base.region", + "fields": { + "rght": 126, + "code": "WAF", + "name": "West Africa", + "parent": 10, + "level": 2, + "lft": 83, + "tree_id": 90, + "bbox_x0": -26.758421, + "bbox_x1": 24.002661, + "bbox_y0": -9.29925, + "bbox_y1": 27.702801 + } + }, + { + "pk": 14, + "model": "base.region", + "fields": { + "rght": 82, + "code": "SAF", + "name": "Southern Africa", + "parent": 10, + "level": 2, + "lft": 65, + "tree_id": 90, + "bbox_x0": 8.93107, + "bbox_x1": 42.74847, + "bbox_y0": -35.507481, + "bbox_y1": -13.27553 + } + }, + { + "pk": 15, + "model": "base.region", + "fields": { + "rght": 463, + "code": "MES", + "name": "Middle East", + "parent": null, + "level": 1, + "lft": 434, + "tree_id": 90, + "bbox_x0": 24.698099, + "bbox_x1": 63.317459, + "bbox_y0": 12.111, + "bbox_y1": 42.10751 + } + }, + { + "pk": 16, + "model": "base.region", + "fields": { + "rght": 245, + "code": "ANT", + "name": "Antarctica", + "parent": null, + "level": 1, + "lft": 244, + "tree_id": 90, + "bbox_x0": -180, + "bbox_x1": 180, + "bbox_y0": -90, + "bbox_y1": -73 + } + }, + { + "pk": 17, + "model": "base.region", + "fields": { + "rght": 249, + "code": "AFG", + "name": "Afghanistan", + "parent": 8, + "level": 3, + "lft": 248, + "tree_id": 90, + "bbox_x0": 60.478439, + "bbox_x1": 74.879433, + "bbox_y0": 29.37747, + "bbox_y1": 38.483421 + } + }, + { + "pk": 18, + "model": "base.region", + "fields": { + "rght": 320, + "code": "ALA", + "name": "Aland Islands", + "parent": 5, + "level": 2, + "lft": 319, + "tree_id": 90, + "bbox_x0": 19.262711, + "bbox_x1": 21.324409, + "bbox_y0": 59.736301, + "bbox_y1": 60.665581 + } + }, + { + "pk": 19, + "model": "base.region", + "fields": { + "rght": 322, + "code": "ALB", + "name": "Albania", + "parent": 5, + "level": 2, + "lft": 321, + "tree_id": 90, + "bbox_x0": 19.28219, + "bbox_x1": 21.057819, + "bbox_y0": 39.644489, + "bbox_y1": 42.660801 + } + }, + { + "pk": 20, + "model": "base.region", + "fields": { + "rght": 51, + "code": "DZA", + "name": "Algeria", + "parent": 11, + "level": 3, + "lft": 50, + "tree_id": 90, + "bbox_x0": -8.67386, + "bbox_x1": 11.97955, + "bbox_y0": 18.96002, + "bbox_y1": 37.093731 + } + }, + { + "pk": 21, + "model": "base.region", + "fields": { + "rght": 466, + "code": "ASM", + "name": "American Samoa", + "parent": 256, + "level": 2, + "lft": 465, + "tree_id": 90, + "bbox_x0": -171.091873, + "bbox_x1": -169.416077, + "bbox_y0": -14.38247, + "bbox_y1": -11.04969 + } + }, + { + "pk": 22, + "model": "base.region", + "fields": { + "rght": 324, + "code": "AND", + "name": "Andorra", + "parent": 5, + "level": 2, + "lft": 323, + "tree_id": 90, + "bbox_x0": 1.41382, + "bbox_x1": 1.78659, + "bbox_y0": 42.42873, + "bbox_y1": 42.65601 + } + }, + { + "pk": 23, + "model": "base.region", + "fields": { + "rght": 85, + "code": "AGO", + "name": "Angola", + "parent": 13, + "level": 3, + "lft": 84, + "tree_id": 90, + "bbox_x0": 11.6792, + "bbox_x1": 24.082109, + "bbox_y0": -18.04207, + "bbox_y1": -4.37259 + } + }, + { + "pk": 24, + "model": "base.region", + "fields": { + "rght": 131, + "code": "AIA", + "name": "Anguilla", + "parent": 255, + "level": 3, + "lft": 130, + "tree_id": 90, + "bbox_x0": -63.434872, + "bbox_x1": -62.916199, + "bbox_y0": 18.149549, + "bbox_y1": 18.61278 + } + }, + { + "pk": 25, + "model": "base.region", + "fields": { + "rght": 133, + "code": "ATG", + "name": "Antigua and Barbuda", + "parent": 255, + "level": 3, + "lft": 132, + "tree_id": 90, + "bbox_x0": -62.352402, + "bbox_x1": -61.659081, + "bbox_y0": 16.927219, + "bbox_y1": 17.72938 + } + }, + { + "pk": 26, + "model": "base.region", + "fields": { + "rght": 215, + "code": "ARG", + "name": "Argentina", + "parent": 4, + "level": 3, + "lft": 214, + "tree_id": 90, + "bbox_x0": -73.577782, + "bbox_x1": -53.637539, + "bbox_y0": -55.057362, + "bbox_y1": -21.78126 + } + }, + { + "pk": 27, + "model": "base.region", + "fields": { + "rght": 326, + "code": "ARM", + "name": "Armenia", + "parent": 5, + "level": 2, + "lft": 325, + "tree_id": 90, + "bbox_x0": 43.449749, + "bbox_x1": 46.630039, + "bbox_y0": 38.830521, + "bbox_y1": 41.30183 + } + }, + { + "pk": 28, + "model": "base.region", + "fields": { + "rght": 135, + "code": "ABW", + "name": "Aruba", + "parent": 255, + "level": 3, + "lft": 134, + "tree_id": 90, + "bbox_x0": -70.0611, + "bbox_x1": -69.8669, + "bbox_y0": 12.4061, + "bbox_y1": 12.6306 + } + }, + { + "pk": 29, + "model": "base.region", + "fields": { + "rght": 468, + "code": "AUS", + "name": "Australia", + "parent": 256, + "level": 2, + "lft": 467, + "tree_id": 90, + "bbox_x0": 112.921112, + "bbox_x1": 159.278717, + "bbox_y0": -54.640301, + "bbox_y1": -9.22882 + } + }, + { + "pk": 30, + "model": "base.region", + "fields": { + "rght": 328, + "code": "AUT", + "name": "Austria", + "parent": 5, + "level": 2, + "lft": 327, + "tree_id": 90, + "bbox_x0": 9.53079, + "bbox_x1": 17.160749, + "bbox_y0": 46.372299, + "bbox_y1": 49.02071 + } + }, + { + "pk": 31, + "model": "base.region", + "fields": { + "rght": 330, + "code": "AZE", + "name": "Azerbaijan", + "parent": 5, + "level": 2, + "lft": 329, + "tree_id": 90, + "bbox_x0": 44.7719, + "bbox_x1": 50.6078, + "bbox_y0": 38.3970, + "bbox_y1": 41.9056 + } + }, + { + "pk": 32, + "model": "base.region", + "fields": { + "rght": 137, + "code": "BHS", + "name": "Bahamas", + "parent": 255, + "level": 3, + "lft": 136, + "tree_id": 90, + "bbox_x0": -80.499229, + "bbox_x1": -72.649513, + "bbox_y0": 20.916059, + "bbox_y1": 27.933781 + } + }, + { + "pk": 33, + "model": "base.region", + "fields": { + "rght": 436, + "code": "BHR", + "name": "Bahrain", + "parent": 15, + "level": 2, + "lft": 435, + "tree_id": 90, + "bbox_x0": 50.385799, + "bbox_x1": 50.828499, + "bbox_y0": 25.5422, + "bbox_y1": 26.292391 + } + }, + { + "pk": 34, + "model": "base.region", + "fields": { + "rght": 279, + "code": "BGD", + "name": "Bangladesh", + "parent": 9, + "level": 3, + "lft": 278, + "tree_id": 90, + "bbox_x0": 88.028198, + "bbox_x1": 92.673599, + "bbox_y0": 20.585199, + "bbox_y1": 26.631701 + } + }, + { + "pk": 35, + "model": "base.region", + "fields": { + "rght": 139, + "code": "BRB", + "name": "Barbados", + "parent": 255, + "level": 3, + "lft": 138, + "tree_id": 90, + "bbox_x0": -59.648918, + "bbox_x1": -59.420368, + "bbox_y0": 13.03984, + "bbox_y1": 13.32725 + } + }, + { + "pk": 36, + "model": "base.region", + "fields": { + "rght": 332, + "code": "BLR", + "name": "Belarus", + "parent": 5, + "level": 2, + "lft": 331, + "tree_id": 90, + "bbox_x0": 23.17679, + "bbox_x1": 32.77071, + "bbox_y0": 51.256401, + "bbox_y1": 56.16571 + } + }, + { + "pk": 37, + "model": "base.region", + "fields": { + "rght": 334, + "code": "BEL", + "name": "Belgium", + "parent": 5, + "level": 2, + "lft": 333, + "tree_id": 90, + "bbox_x0": 2.54563, + "bbox_x1": 6.40791, + "bbox_y0": 49.496899, + "bbox_y1": 51.505081 + } + }, + { + "pk": 38, + "model": "base.region", + "fields": { + "rght": 189, + "code": "BLZ", + "name": "Belize", + "parent": 3, + "level": 3, + "lft": 188, + "tree_id": 90, + "bbox_x0": -89.224823, + "bbox_x1": -87.468132, + "bbox_y0": 15.8893, + "bbox_y1": 18.49655 + } + }, + { + "pk": 39, + "model": "base.region", + "fields": { + "rght": 87, + "code": "BEN", + "name": "Benin", + "parent": 13, + "level": 3, + "lft": 86, + "tree_id": 90, + "bbox_x0": 0.77456, + "bbox_x1": 3.8517, + "bbox_y0": 6.22574, + "bbox_y1": 12.41834 + } + }, + { + "pk": 40, + "model": "base.region", + "fields": { + "rght": 141, + "code": "BMU", + "name": "Bermuda", + "parent": 255, + "level": 3, + "lft": 140, + "tree_id": 90, + "bbox_x0": -64.896042, + "bbox_x1": -64.642952, + "bbox_y0": 32.230709, + "bbox_y1": 32.393829 + } + }, + { + "pk": 41, + "model": "base.region", + "fields": { + "rght": 281, + "code": "BTN", + "name": "Bhutan", + "parent": 9, + "level": 3, + "lft": 280, + "tree_id": 90, + "bbox_x0": 88.759521, + "bbox_x1": 92.125023, + "bbox_y0": 26.7075, + "bbox_y1": 28.3235 + } + }, + { + "pk": 42, + "model": "base.region", + "fields": { + "rght": 217, + "code": "BOL", + "name": "Bolivia", + "parent": 4, + "level": 3, + "lft": 216, + "tree_id": 90, + "bbox_x0": -69.640762, + "bbox_x1": -57.458092, + "bbox_y0": -22.89613, + "bbox_y1": -9.68056 + } + }, + { + "pk": 43, + "model": "base.region", + "fields": { + "rght": 336, + "code": "BIH", + "name": "Bosnia and Herzegovina", + "parent": 5, + "level": 2, + "lft": 335, + "tree_id": 90, + "bbox_x0": 15.74909, + "bbox_x1": 19.62907, + "bbox_y0": 42.56451, + "bbox_y1": 45.276001 + } + }, + { + "pk": 44, + "model": "base.region", + "fields": { + "rght": 67, + "code": "BWA", + "name": "Botswana", + "parent": 14, + "level": 3, + "lft": 66, + "tree_id": 90, + "bbox_x0": 19.999531, + "bbox_x1": 29.360781, + "bbox_y0": -26.90724, + "bbox_y1": -17.780809 + } + }, + { + "pk": 45, + "model": "base.region", + "fields": { + "rght": 219, + "code": "BRA", + "name": "Brazil", + "parent": 4, + "level": 3, + "lft": 218, + "tree_id": 90, + "bbox_x0": -73.985527, + "bbox_x1": -28.839041, + "bbox_y0": -33.750702, + "bbox_y1": 5.26486 + } + }, + { + "pk": 46, + "model": "base.region", + "fields": { + "rght": 143, + "code": "VGB", + "name": "British Virgin Islands", + "parent": 255, + "level": 3, + "lft": 142, + "tree_id": 90, + "bbox_x0": -64.783012, + "bbox_x1": -64.268761, + "bbox_y0": 18.312731, + "bbox_y1": 18.757219 + } + }, + { + "pk": 47, + "model": "base.region", + "fields": { + "rght": 295, + "code": "BRN", + "name": "Brunei Darussalam", + "parent": 7, + "level": 3, + "lft": 294, + "tree_id": 90, + "bbox_x0": 114.071457, + "bbox_x1": 115.359451, + "bbox_y0": 4.00309, + "bbox_y1": 5.04717 + } + }, + { + "pk": 48, + "model": "base.region", + "fields": { + "rght": 338, + "code": "BGR", + "name": "Bulgaria", + "parent": 5, + "level": 2, + "lft": 337, + "tree_id": 90, + "bbox_x0": 22.35741, + "bbox_x1": 28.60882, + "bbox_y0": 41.235931, + "bbox_y1": 44.227261 + } + }, + { + "pk": 49, + "model": "base.region", + "fields": { + "rght": 89, + "code": "BFA", + "name": "Burkina Faso", + "parent": 13, + "level": 3, + "lft": 88, + "tree_id": 90, + "bbox_x0": -5.51891, + "bbox_x1": 2.40539, + "bbox_y0": 9.4011, + "bbox_y1": 15.08259 + } + }, + { + "pk": 50, + "model": "base.region", + "fields": { + "rght": 15, + "code": "BDI", + "name": "Burundi", + "parent": 12, + "level": 3, + "lft": 14, + "tree_id": 90, + "bbox_x0": 28.993071, + "bbox_x1": 30.847719, + "bbox_y0": -4.46571, + "bbox_y1": -2.31012 + } + }, + { + "pk": 51, + "model": "base.region", + "fields": { + "rght": 297, + "code": "KHM", + "name": "Cambodia", + "parent": 7, + "level": 3, + "lft": 296, + "tree_id": 90, + "bbox_x0": 102.340012, + "bbox_x1": 107.627724, + "bbox_y0": 9.28325, + "bbox_y1": 14.6864 + } + }, + { + "pk": 52, + "model": "base.region", + "fields": { + "rght": 91, + "code": "CMR", + "name": "Cameroon", + "parent": 13, + "level": 3, + "lft": 90, + "tree_id": 90, + "bbox_x0": 8.49477, + "bbox_x1": 16.19211, + "bbox_y0": 1.65254, + "bbox_y1": 13.07805 + } + }, + { + "pk": 53, + "model": "base.region", + "fields": { + "rght": 205, + "code": "CAN", + "name": "Canada", + "parent": 2, + "level": 3, + "lft": 204, + "tree_id": 90, + "bbox_x0": -141.002701, + "bbox_x1": -52.620201, + "bbox_y0": 41.681019, + "bbox_y1": 83.110619 + } + }, + { + "pk": 54, + "model": "base.region", + "fields": { + "rght": 93, + "code": "CPV", + "name": "Cape Verde", + "parent": 13, + "level": 3, + "lft": 92, + "tree_id": 90, + "bbox_x0": -25.35874, + "bbox_x1": -22.666201, + "bbox_y0": 14.80221, + "bbox_y1": 17.19717 + } + }, + { + "pk": 55, + "model": "base.region", + "fields": { + "rght": 145, + "code": "CYM", + "name": "Cayman Islands", + "parent": 255, + "level": 3, + "lft": 144, + "tree_id": 90, + "bbox_x0": -81.420593, + "bbox_x1": -79.722321, + "bbox_y0": 19.262659, + "bbox_y1": 19.75738 + } + }, + { + "pk": 56, + "model": "base.region", + "fields": { + "rght": 5, + "code": "CAF", + "name": "Central African Republic", + "parent": 257, + "level": 3, + "lft": 4, + "tree_id": 90, + "bbox_x0": 14.42009, + "bbox_x1": 27.463421, + "bbox_y0": 2.22051, + "bbox_y1": 11.00756 + } + }, + { + "pk": 57, + "model": "base.region", + "fields": { + "rght": 7, + "code": "TCD", + "name": "Chad", + "parent": 257, + "level": 3, + "lft": 6, + "tree_id": 90, + "bbox_x0": 13.47592, + "bbox_x1": 24.00161, + "bbox_y0": 7.44237, + "bbox_y1": 23.478239 + } + }, + { + "pk": 58, + "model": "base.region", + "fields": { + "rght": 340, + "code": "CIL", + "name": "Channel Islands", + "parent": 5, + "level": 2, + "lft": 339, + "tree_id": 90, + "bbox_x0": -2.67545, + "bbox_x1": -2.01129, + "bbox_y0": 49.16209, + "bbox_y1": 49.7393 + } + }, + { + "pk": 59, + "model": "base.region", + "fields": { + "rght": 221, + "code": "CHL", + "name": "Chile", + "parent": 4, + "level": 3, + "lft": 220, + "tree_id": 90, + "bbox_x0": -109.47493, + "bbox_x1": -66.417549, + "bbox_y0": -56.533779, + "bbox_y1": -17.507549 + } + }, + { + "pk": 60, + "model": "base.region", + "fields": { + "rght": 263, + "code": "CHN", + "name": "China", + "parent": 258, + "level": 3, + "lft": 262, + "tree_id": 90, + "bbox_x0": 73.557701, + "bbox_x1": 134.773605, + "bbox_y0": 15.77539, + "bbox_y1": 53.5606 + } + }, + { + "pk": 61, + "model": "base.region", + "fields": { + "rght": 265, + "code": "HKG", + "name": "China - Hong Kong", + "parent": 258, + "level": 3, + "lft": 264, + "tree_id": 90, + "bbox_x0": 113.835083, + "bbox_x1": 114.441788, + "bbox_y0": 22.153549, + "bbox_y1": 22.56204 + } + }, + { + "pk": 62, + "model": "base.region", + "fields": { + "rght": 267, + "code": "MAC", + "name": "China - Macao", + "parent": 258, + "level": 3, + "lft": 266, + "tree_id": 90, + "bbox_x0": 113.528351, + "bbox_x1": 113.598297, + "bbox_y0": 22.10977, + "bbox_y1": 22.21697 + } + }, + { + "pk": 63, + "model": "base.region", + "fields": { + "rght": 223, + "code": "COL", + "name": "Colombia", + "parent": 4, + "level": 3, + "lft": 222, + "tree_id": 90, + "bbox_x0": -81.728111, + "bbox_x1": -66.869827, + "bbox_y0": -4.23048, + "bbox_y1": 13.39029 + } + }, + { + "pk": 64, + "model": "base.region", + "fields": { + "rght": 17, + "code": "COM", + "name": "Comoros", + "parent": 12, + "level": 3, + "lft": 16, + "tree_id": 90, + "bbox_x0": 43.215778, + "bbox_x1": 44.538219, + "bbox_y0": -12.41382, + "bbox_y1": -11.36238 + } + }, + { + "pk": 65, + "model": "base.region", + "fields": { + "rght": 9, + "code": "COG", + "name": "Congo", + "parent": 257, + "level": 3, + "lft": 8, + "tree_id": 90, + "bbox_x0": 11.205, + "bbox_x1": 18.64983, + "bbox_y0": -5.02831, + "bbox_y1": 3.70308 + } + }, + { + "pk": 66, + "model": "base.region", + "fields": { + "rght": 470, + "code": "COK", + "name": "Cook Islands", + "parent": 256, + "level": 2, + "lft": 469, + "tree_id": 90, + "bbox_x0": -165.858093, + "bbox_x1": -157.312119, + "bbox_y0": -21.94416, + "bbox_y1": -8.94402 + } + }, + { + "pk": 67, + "model": "base.region", + "fields": { + "rght": 191, + "code": "CRI", + "name": "Costa Rica", + "parent": 3, + "level": 3, + "lft": 190, + "tree_id": 90, + "bbox_x0": -87.083778, + "bbox_x1": -82.556, + "bbox_y0": 5.49955, + "bbox_y1": 11.21681 + } + }, + { + "pk": 68, + "model": "base.region", + "fields": { + "rght": 95, + "code": "CIV", + "name": "Cote d'Ivoire", + "parent": 13, + "level": 3, + "lft": 94, + "tree_id": 90, + "bbox_x0": -8.6017249, + "bbox_x1": -2.4930309, + "bbox_y0": 4.1642077, + "bbox_y1": 10.740015 + } + }, + { + "pk": 69, + "model": "base.region", + "fields": { + "rght": 342, + "code": "HRV", + "name": "Croatia", + "parent": 5, + "level": 2, + "lft": 341, + "tree_id": 90, + "bbox_x0": 13.48972, + "bbox_x1": 19.44722, + "bbox_y0": 42.392208, + "bbox_y1": 46.554981 + } + }, + { + "pk": 70, + "model": "base.region", + "fields": { + "rght": 147, + "code": "CUB", + "name": "Cuba", + "parent": 255, + "level": 3, + "lft": 146, + "tree_id": 90, + "bbox_x0": -84.957428, + "bbox_x1": -74.131783, + "bbox_y0": 19.828079, + "bbox_y1": 23.283779 + } + }, + { + "pk": 71, + "model": "base.region", + "fields": { + "rght": 344, + "code": "CYP", + "name": "Cyprus", + "parent": 5, + "level": 2, + "lft": 343, + "tree_id": 90, + "bbox_x0": 32.27309, + "bbox_x1": 34.597919, + "bbox_y0": 34.563511, + "bbox_y1": 35.701542 + } + }, + { + "pk": 72, + "model": "base.region", + "fields": { + "rght": 346, + "code": "CZE", + "name": "Czech Republic", + "parent": 5, + "level": 2, + "lft": 345, + "tree_id": 90, + "bbox_x0": 12.0905901, + "bbox_x1": 18.859216, + "bbox_y0": 48.5518144, + "bbox_y1": 51.0557036 + } + }, + { + "pk": 73, + "model": "base.region", + "fields": { + "rght": 269, + "code": "PRK", + "name": "Democratic People's Republic of Korea", + "parent": 258, + "level": 3, + "lft": 268, + "tree_id": 90, + "bbox_x0": 124.182739, + "bbox_x1": 130.674713, + "bbox_y0": 37.632881, + "bbox_y1": 43.006001 + } + }, + { + "pk": 74, + "model": "base.region", + "fields": { + "rght": 11, + "code": "COD", + "name": "Democratic Republic of the Congo", + "parent": 257, + "level": 3, + "lft": 10, + "tree_id": 90, + "bbox_x0": 12.20663, + "bbox_x1": 31.30591, + "bbox_y0": -13.45567, + "bbox_y1": 5.38609 + } + }, + { + "pk": 75, + "model": "base.region", + "fields": { + "rght": 348, + "code": "DNK", + "name": "Denmark", + "parent": 5, + "level": 2, + "lft": 347, + "tree_id": 90, + "bbox_x0": 8.07472, + "bbox_x1": 15.19324, + "bbox_y0": 54.559132, + "bbox_y1": 57.751949 + } + }, + { + "pk": 76, + "model": "base.region", + "fields": { + "rght": 19, + "code": "DJI", + "name": "Djibouti", + "parent": 12, + "level": 3, + "lft": 18, + "tree_id": 90, + "bbox_x0": 41.773441, + "bbox_x1": 43.450459, + "bbox_y0": 10.90991, + "bbox_y1": 12.70683 + } + }, + { + "pk": 77, + "model": "base.region", + "fields": { + "rght": 149, + "code": "DMA", + "name": "Dominica", + "parent": 255, + "level": 3, + "lft": 148, + "tree_id": 90, + "bbox_x0": -61.4841, + "bbox_x1": -61.244148, + "bbox_y0": 15.20168, + "bbox_y1": 15.6318 + } + }, + { + "pk": 78, + "model": "base.region", + "fields": { + "rght": 151, + "code": "DOM", + "name": "Dominican Republic", + "parent": 255, + "level": 3, + "lft": 150, + "tree_id": 90, + "bbox_x0": -72.003479, + "bbox_x1": -68.319992, + "bbox_y0": 17.469299, + "bbox_y1": 19.92985 + } + }, + { + "pk": 79, + "model": "base.region", + "fields": { + "rght": 225, + "code": "ECU", + "name": "Ecuador", + "parent": 4, + "level": 3, + "lft": 224, + "tree_id": 90, + "bbox_x0": -91.66124, + "bbox_x1": -75.200073, + "bbox_y0": -5.01734, + "bbox_y1": 1.45421 + } + }, + { + "pk": 80, + "model": "base.region", + "fields": { + "rght": 53, + "code": "EGY", + "name": "Egypt", + "parent": 11, + "level": 3, + "lft": 52, + "tree_id": 90, + "bbox_x0": 24.698099, + "bbox_x1": 36.89468, + "bbox_y0": 22, + "bbox_y1": 31.674179 + } + }, + { + "pk": 81, + "model": "base.region", + "fields": { + "rght": 193, + "code": "SLV", + "name": "El Salvador", + "parent": 3, + "level": 3, + "lft": 192, + "tree_id": 90, + "bbox_x0": -90.12867, + "bbox_x1": -87.682869, + "bbox_y0": 13.14867, + "bbox_y1": 14.44506 + } + }, + { + "pk": 82, + "model": "base.region", + "fields": { + "rght": 97, + "code": "GNQ", + "name": "Equatorial Guinea", + "parent": 13, + "level": 3, + "lft": 96, + "tree_id": 90, + "bbox_x0": 5.60236, + "bbox_x1": 11.33572, + "bbox_y0": -1.48378, + "bbox_y1": 3.78597 + } + }, + { + "pk": 83, + "model": "base.region", + "fields": { + "rght": 21, + "code": "ERI", + "name": "Eritrea", + "parent": 12, + "level": 3, + "lft": 20, + "tree_id": 90, + "bbox_x0": 36.43877, + "bbox_x1": 43.14864, + "bbox_y0": 12.35956, + "bbox_y1": 18.00308 + } + }, + { + "pk": 84, + "model": "base.region", + "fields": { + "rght": 350, + "code": "EST", + "name": "Estonia", + "parent": 5, + "level": 2, + "lft": 349, + "tree_id": 90, + "bbox_x0": 21.771851, + "bbox_x1": 28.20989, + "bbox_y0": 57.509312, + "bbox_y1": 59.685749 + } + }, + { + "pk": 85, + "model": "base.region", + "fields": { + "rght": 23, + "code": "ETH", + "name": "Ethiopia", + "parent": 12, + "level": 3, + "lft": 22, + "tree_id": 90, + "bbox_x0": 32.99992, + "bbox_x1": 47.986172, + "bbox_y0": 3.40242, + "bbox_y1": 14.89218 + } + }, + { + "pk": 86, + "model": "base.region", + "fields": { + "rght": 352, + "code": "FRO", + "name": "Faeroe Islands", + "parent": 5, + "level": 2, + "lft": 351, + "tree_id": 90, + "bbox_x0": -7.68124, + "bbox_x1": -6.25861, + "bbox_y0": 61.394932, + "bbox_y1": 62.400742 + } + }, + { + "pk": 87, + "model": "base.region", + "fields": { + "rght": 227, + "code": "FLK", + "name": "Falkland Islands (Malvinas)", + "parent": 4, + "level": 3, + "lft": 226, + "tree_id": 90, + "bbox_x0": -61.43404, + "bbox_x1": -57.712479, + "bbox_y0": -52.900581, + "bbox_y1": -50.966221 + } + }, + { + "pk": 88, + "model": "base.region", + "fields": { + "rght": 472, + "code": "FJI", + "name": "Fiji", + "parent": 256, + "level": 2, + "lft": 471, + "tree_id": 90, + "bbox_x0": 174.866196, + "bbox_x1": -178.203156, + "bbox_y0": -21.01712, + "bbox_y1": -12.46622 + } + }, + { + "pk": 89, + "model": "base.region", + "fields": { + "rght": 354, + "code": "FIN", + "name": "Finland", + "parent": 5, + "level": 2, + "lft": 353, + "tree_id": 90, + "bbox_x0": 20.548571, + "bbox_x1": 31.586201, + "bbox_y0": 59.764881, + "bbox_y1": 70.092308 + } + }, + { + "pk": 90, + "model": "base.region", + "fields": { + "rght": 356, + "code": "FRA", + "name": "France", + "parent": 5, + "level": 2, + "lft": 355, + "tree_id": 90, + "bbox_x0": -5.1406, + "bbox_x1": 9.55932, + "bbox_y0": 41.33374, + "bbox_y1": 51.089062 + } + }, + { + "pk": 91, + "model": "base.region", + "fields": { + "rght": 229, + "code": "GUF", + "name": "French Guiana", + "parent": 4, + "level": 3, + "lft": 228, + "tree_id": 90, + "bbox_x0": -54.542511, + "bbox_x1": -51.613941, + "bbox_y0": 2.12709, + "bbox_y1": 5.77649 + } + }, + { + "pk": 92, + "model": "base.region", + "fields": { + "rght": 474, + "code": "PYF", + "name": "French Polynesia", + "parent": 256, + "level": 2, + "lft": 473, + "tree_id": 90, + "bbox_x0": -154.700485, + "bbox_x1": -108.87291, + "bbox_y0": -27.65357, + "bbox_y1": 10.35983 + } + }, + { + "pk": 93, + "model": "base.region", + "fields": { + "rght": 99, + "code": "GAB", + "name": "Gabon", + "parent": 13, + "level": 3, + "lft": 98, + "tree_id": 90, + "bbox_x0": 8.69547, + "bbox_x1": 14.50234, + "bbox_y0": -3.9788, + "bbox_y1": 2.32261 + } + }, + { + "pk": 94, + "model": "base.region", + "fields": { + "rght": 101, + "code": "GMB", + "name": "Gambia", + "parent": 13, + "level": 3, + "lft": 100, + "tree_id": 90, + "bbox_x0": -16.82506, + "bbox_x1": -13.7978, + "bbox_y0": 13.06425, + "bbox_y1": 13.82657 + } + }, + { + "pk": 95, + "model": "base.region", + "fields": { + "rght": 358, + "code": "GEO", + "name": "Georgia", + "parent": 5, + "level": 2, + "lft": 357, + "tree_id": 90, + "bbox_x0": 40.01022, + "bbox_x1": 46.721359, + "bbox_y0": 41.038502, + "bbox_y1": 43.584549 + } + }, + { + "pk": 96, + "model": "base.region", + "fields": { + "rght": 360, + "code": "DEU", + "name": "Germany", + "parent": 5, + "level": 2, + "lft": 359, + "tree_id": 90, + "bbox_x0": 5.86624, + "bbox_x1": 15.04205, + "bbox_y0": 47.27021, + "bbox_y1": 55.05814 + } + }, + { + "pk": 97, + "model": "base.region", + "fields": { + "rght": 103, + "code": "GHA", + "name": "Ghana", + "parent": 13, + "level": 3, + "lft": 102, + "tree_id": 90, + "bbox_x0": -3.25542, + "bbox_x1": 1.19178, + "bbox_y0": 4.73672, + "bbox_y1": 11.1733 + } + }, + { + "pk": 98, + "model": "base.region", + "fields": { + "rght": 362, + "code": "GIB", + "name": "Gibraltar", + "parent": 5, + "level": 2, + "lft": 361, + "tree_id": 90, + "bbox_x0": -5.3579, + "bbox_x1": -5.33867, + "bbox_y0": 36.108219, + "bbox_y1": 36.15593 + } + }, + { + "pk": 99, + "model": "base.region", + "fields": { + "rght": 364, + "code": "GRC", + "name": "Greece", + "parent": 5, + "level": 2, + "lft": 363, + "tree_id": 90, + "bbox_x0": 19.37431, + "bbox_x1": 29.70056, + "bbox_y0": 34.809502, + "bbox_y1": 41.757111 + } + }, + { + "pk": 100, + "model": "base.region", + "fields": { + "rght": 207, + "code": "GRL", + "name": "Greenland", + "parent": 2, + "level": 3, + "lft": 206, + "tree_id": 90, + "bbox_x0": -73.263474, + "bbox_x1": -11.31232, + "bbox_y0": 59.777271, + "bbox_y1": 83.627419 + } + }, + { + "pk": 101, + "model": "base.region", + "fields": { + "rght": 153, + "code": "GRD", + "name": "Grenada", + "parent": 255, + "level": 3, + "lft": 152, + "tree_id": 90, + "bbox_x0": -61.79998, + "bbox_x1": -61.376362, + "bbox_y0": 11.98288, + "bbox_y1": 12.5415 + } + }, + { + "pk": 102, + "model": "base.region", + "fields": { + "rght": 155, + "code": "GLP", + "name": "Guadeloupe", + "parent": 255, + "level": 3, + "lft": 154, + "tree_id": 90, + "bbox_x0": -61.807159, + "bbox_x1": -61, + "bbox_y0": 15.83097, + "bbox_y1": 16.51684 + } + }, + { + "pk": 103, + "model": "base.region", + "fields": { + "rght": 476, + "code": "GUM", + "name": "Guam", + "parent": 256, + "level": 2, + "lft": 475, + "tree_id": 90, + "bbox_x0": 144.619263, + "bbox_x1": 144.953995, + "bbox_y0": 13.24059, + "bbox_y1": 13.65232 + } + }, + { + "pk": 104, + "model": "base.region", + "fields": { + "rght": 195, + "code": "GTM", + "name": "Guatemala", + "parent": 3, + "level": 3, + "lft": 194, + "tree_id": 90, + "bbox_x0": -92.241432, + "bbox_x1": -88.22319, + "bbox_y0": 13.7373, + "bbox_y1": 17.815201 + } + }, + { + "pk": 105, + "model": "base.region", + "fields": { + "rght": 366, + "code": "GGY", + "name": "Guernsey", + "parent": 5, + "level": 2, + "lft": 365, + "tree_id": 90, + "bbox_x0": -2.67545, + "bbox_x1": -2.16382, + "bbox_y0": 49.405762, + "bbox_y1": 49.7393 + } + }, + { + "pk": 106, + "model": "base.region", + "fields": { + "rght": 105, + "code": "GIN", + "name": "Guinea", + "parent": 13, + "level": 3, + "lft": 104, + "tree_id": 90, + "bbox_x0": -15.08625, + "bbox_x1": -7.64106, + "bbox_y0": 7.19355, + "bbox_y1": 12.67621 + } + }, + { + "pk": 107, + "model": "base.region", + "fields": { + "rght": 107, + "code": "GNB", + "name": "Guinea-Bissau", + "parent": 13, + "level": 3, + "lft": 106, + "tree_id": 90, + "bbox_x0": -16.717529, + "bbox_x1": -13.63652, + "bbox_y0": 10.85997, + "bbox_y1": 12.68078 + } + }, + { + "pk": 108, + "model": "base.region", + "fields": { + "rght": 231, + "code": "GUY", + "name": "Guyana", + "parent": 4, + "level": 3, + "lft": 230, + "tree_id": 90, + "bbox_x0": -61.396271, + "bbox_x1": -56.480251, + "bbox_y0": 1.17508, + "bbox_y1": 8.55756 + } + }, + { + "pk": 109, + "model": "base.region", + "fields": { + "rght": 157, + "code": "HTI", + "name": "Haiti", + "parent": 255, + "level": 3, + "lft": 156, + "tree_id": 90, + "bbox_x0": -74.478592, + "bbox_x1": -71.61335, + "bbox_y0": 18.02103, + "bbox_y1": 20.08782 + } + }, + { + "pk": 110, + "model": "base.region", + "fields": { + "rght": 368, + "code": "VAT", + "name": "Holy See (Vatican City)", + "parent": 5, + "level": 2, + "lft": 367, + "tree_id": 90, + "bbox_x0": 12.44584, + "bbox_x1": 12.45842, + "bbox_y0": 41.900211, + "bbox_y1": 41.907459 + } + }, + { + "pk": 111, + "model": "base.region", + "fields": { + "rght": 197, + "code": "HND", + "name": "Honduras", + "parent": 3, + "level": 3, + "lft": 196, + "tree_id": 90, + "bbox_x0": -89.350792, + "bbox_x1": -82.499527, + "bbox_y0": 12.98241, + "bbox_y1": 17.450451 + } + }, + { + "pk": 112, + "model": "base.region", + "fields": { + "rght": 370, + "code": "HUN", + "name": "Hungary", + "parent": 5, + "level": 2, + "lft": 369, + "tree_id": 90, + "bbox_x0": 16.11335, + "bbox_x1": 22.89657, + "bbox_y0": 45.737061, + "bbox_y1": 48.585258 + } + }, + { + "pk": 113, + "model": "base.region", + "fields": { + "rght": 372, + "code": "ISL", + "name": "Iceland", + "parent": 5, + "level": 2, + "lft": 371, + "tree_id": 90, + "bbox_x0": -24.54652, + "bbox_x1": -13.49416, + "bbox_y0": 63.295952, + "bbox_y1": 66.566193 + } + }, + { + "pk": 114, + "model": "base.region", + "fields": { + "rght": 283, + "code": "IND", + "name": "India", + "parent": 9, + "level": 3, + "lft": 282, + "tree_id": 90, + "bbox_x0": 68.032318, + "bbox_x1": 97.403023, + "bbox_y0": 6.7471, + "bbox_y1": 36.261688 + } + }, + { + "pk": 115, + "model": "base.region", + "fields": { + "rght": 299, + "code": "IDN", + "name": "Indonesia", + "parent": 7, + "level": 3, + "lft": 298, + "tree_id": 90, + "bbox_x0": 94.969833, + "bbox_x1": 141.021805, + "bbox_y0": -11.00485, + "bbox_y1": 6.07573 + } + }, + { + "pk": 116, + "model": "base.region", + "fields": { + "rght": 438, + "code": "IRN", + "name": "Iran", + "parent": 15, + "level": 2, + "lft": 437, + "tree_id": 90, + "bbox_x0": 44.047249, + "bbox_x1": 63.317459, + "bbox_y0": 25.064079, + "bbox_y1": 39.777222 + } + }, + { + "pk": 117, + "model": "base.region", + "fields": { + "rght": 440, + "code": "IRQ", + "name": "Iraq", + "parent": 15, + "level": 2, + "lft": 439, + "tree_id": 90, + "bbox_x0": 38.804001, + "bbox_x1": 48.575699, + "bbox_y0": 29.103001, + "bbox_y1": 37.378052 + } + }, + { + "pk": 118, + "model": "base.region", + "fields": { + "rght": 374, + "code": "IRL", + "name": "Ireland", + "parent": 5, + "level": 2, + "lft": 373, + "tree_id": 90, + "bbox_x0": -10.61834, + "bbox_x1": -5.9975, + "bbox_y0": 51.424511, + "bbox_y1": 55.436211 + } + }, + { + "pk": 119, + "model": "base.region", + "fields": { + "rght": 376, + "code": "IMN", + "name": "Isle of Man", + "parent": 5, + "level": 2, + "lft": 375, + "tree_id": 90, + "bbox_x0": -4.83018, + "bbox_x1": -4.31007, + "bbox_y0": 54.04464, + "bbox_y1": 54.418839 + } + }, + { + "pk": 120, + "model": "base.region", + "fields": { + "rght": 442, + "code": "ISR", + "name": "Israel", + "parent": 15, + "level": 2, + "lft": 441, + "tree_id": 90, + "bbox_x0": 34.2677, + "bbox_x1": 35.940941, + "bbox_y0": 29.4965, + "bbox_y1": 33.43338 + } + }, + { + "pk": 121, + "model": "base.region", + "fields": { + "rght": 378, + "code": "ITA", + "name": "Italy", + "parent": 5, + "level": 2, + "lft": 377, + "tree_id": 90, + "bbox_x0": 6.62665, + "bbox_x1": 18.520281, + "bbox_y0": 35.49308, + "bbox_y1": 47.091999 + } + }, + { + "pk": 122, + "model": "base.region", + "fields": { + "rght": 159, + "code": "JAM", + "name": "Jamaica", + "parent": 255, + "level": 3, + "lft": 158, + "tree_id": 90, + "bbox_x0": -78.366638, + "bbox_x1": -75.982857, + "bbox_y0": 16.949551, + "bbox_y1": 18.52697 + } + }, + { + "pk": 123, + "model": "base.region", + "fields": { + "rght": 271, + "code": "JPN", + "name": "Japan", + "parent": 258, + "level": 3, + "lft": 270, + "tree_id": 90, + "bbox_x0": 122.933647, + "bbox_x1": 153.986847, + "bbox_y0": 20.4251, + "bbox_y1": 45.557709 + } + }, + { + "pk": 124, + "model": "base.region", + "fields": { + "rght": 380, + "code": "JEY", + "name": "Jersey", + "parent": 5, + "level": 2, + "lft": 379, + "tree_id": 90, + "bbox_x0": -2.25505, + "bbox_x1": -2.01129, + "bbox_y0": 49.16209, + "bbox_y1": 49.26231 + } + }, + { + "pk": 125, + "model": "base.region", + "fields": { + "rght": 444, + "code": "JOR", + "name": "Jordan", + "parent": 15, + "level": 2, + "lft": 443, + "tree_id": 90, + "bbox_x0": 34.960232, + "bbox_x1": 39.301128, + "bbox_y0": 29.18409, + "bbox_y1": 33.374828 + } + }, + { + "pk": 126, + "model": "base.region", + "fields": { + "rght": 251, + "code": "KAZ", + "name": "Kazakhstan", + "parent": 8, + "level": 3, + "lft": 250, + "tree_id": 90, + "bbox_x0": 46.491859, + "bbox_x1": 87.312737, + "bbox_y0": 40.566689, + "bbox_y1": 55.431808 + } + }, + { + "pk": 127, + "model": "base.region", + "fields": { + "rght": 25, + "code": "KEN", + "name": "Kenya", + "parent": 12, + "level": 3, + "lft": 24, + "tree_id": 90, + "bbox_x0": 33.90884, + "bbox_x1": 41.899059, + "bbox_y0": -4.71712, + "bbox_y1": 4.62933 + } + }, + { + "pk": 128, + "model": "base.region", + "fields": { + "rght": 478, + "code": "KIR", + "name": "Kiribati", + "parent": 256, + "level": 2, + "lft": 477, + "tree_id": 90, + "bbox_x0": 158.418335, + "bbox_x1": -150.208359, + "bbox_y0": -11.43703, + "bbox_y1": 4.71956 + } + }, + { + "pk": 129, + "model": "base.region", + "fields": { + "rght": 446, + "code": "KWT", + "name": "Kuwait", + "parent": 15, + "level": 2, + "lft": 445, + "tree_id": 90, + "bbox_x0": 46.55751, + "bbox_x1": 48.78384, + "bbox_y0": 28.5245, + "bbox_y1": 30.0958 + } + }, + { + "pk": 130, + "model": "base.region", + "fields": { + "rght": 253, + "code": "KGZ", + "name": "Kyrgyzstan", + "parent": 8, + "level": 3, + "lft": 252, + "tree_id": 90, + "bbox_x0": 69.276619, + "bbox_x1": 80.28318, + "bbox_y0": 39.17284, + "bbox_y1": 43.238239 + } + }, + { + "pk": 131, + "model": "base.region", + "fields": { + "rght": 301, + "code": "LAO", + "name": "Lao People's Democratic Republic", + "parent": 7, + "level": 3, + "lft": 300, + "tree_id": 90, + "bbox_x0": 100.093048, + "bbox_x1": 107.697021, + "bbox_y0": 13.91002, + "bbox_y1": 22.500389 + } + }, + { + "pk": 132, + "model": "base.region", + "fields": { + "rght": 382, + "code": "LVA", + "name": "Latvia", + "parent": 5, + "level": 2, + "lft": 381, + "tree_id": 90, + "bbox_x0": 20.966061, + "bbox_x1": 28.244431, + "bbox_y0": 55.67276, + "bbox_y1": 58.087448 + } + }, + { + "pk": 133, + "model": "base.region", + "fields": { + "rght": 448, + "code": "LBN", + "name": "Lebanon", + "parent": 15, + "level": 2, + "lft": 447, + "tree_id": 90, + "bbox_x0": 35.10368, + "bbox_x1": 36.622791, + "bbox_y0": 33.048908, + "bbox_y1": 34.69268 + } + }, + { + "pk": 134, + "model": "base.region", + "fields": { + "rght": 69, + "code": "LSO", + "name": "Lesotho", + "parent": 14, + "level": 3, + "lft": 68, + "tree_id": 90, + "bbox_x0": 27.02906, + "bbox_x1": 29.465771, + "bbox_y0": -30.668961, + "bbox_y1": -28.57205 + } + }, + { + "pk": 135, + "model": "base.region", + "fields": { + "rght": 109, + "code": "LBR", + "name": "Liberia", + "parent": 13, + "level": 3, + "lft": 108, + "tree_id": 90, + "bbox_x0": -11.49208, + "bbox_x1": -7.36511, + "bbox_y0": 4.35305, + "bbox_y1": 8.55179 + } + }, + { + "pk": 136, + "model": "base.region", + "fields": { + "rght": 55, + "code": "LBY", + "name": "Libyan Arab Jamahiriya", + "parent": 11, + "level": 3, + "lft": 54, + "tree_id": 90, + "bbox_x0": 9.38702, + "bbox_x1": 25.15061, + "bbox_y0": 19.508039, + "bbox_y1": 33.168999 + } + }, + { + "pk": 137, + "model": "base.region", + "fields": { + "rght": 384, + "code": "LIE", + "name": "Liechtenstein", + "parent": 5, + "level": 2, + "lft": 383, + "tree_id": 90, + "bbox_x0": 9.47181, + "bbox_x1": 9.63578, + "bbox_y0": 47.04834, + "bbox_y1": 47.270649 + } + }, + { + "pk": 138, + "model": "base.region", + "fields": { + "rght": 386, + "code": "LTU", + "name": "Lithuania", + "parent": 5, + "level": 2, + "lft": 385, + "tree_id": 90, + "bbox_x0": 20.953199, + "bbox_x1": 26.835581, + "bbox_y0": 53.89748, + "bbox_y1": 56.450432 + } + }, + { + "pk": 139, + "model": "base.region", + "fields": { + "rght": 388, + "code": "LUX", + "name": "Luxembourg", + "parent": 5, + "level": 2, + "lft": 387, + "tree_id": 90, + "bbox_x0": 5.73579, + "bbox_x1": 6.53117, + "bbox_y0": 49.447689, + "bbox_y1": 50.182751 + } + }, + { + "pk": 140, + "model": "base.region", + "fields": { + "rght": 390, + "code": "MKD", + "name": "Macedonia", + "parent": 5, + "level": 2, + "lft": 389, + "tree_id": 90, + "bbox_x0": 20.4645, + "bbox_x1": 23.038071, + "bbox_y0": 40.860111, + "bbox_y1": 42.345501 + } + }, + { + "pk": 141, + "model": "base.region", + "fields": { + "rght": 27, + "code": "MDG", + "name": "Madagascar", + "parent": 12, + "level": 3, + "lft": 26, + "tree_id": 90, + "bbox_x0": 43.19138, + "bbox_x1": 50.483799, + "bbox_y0": -25.60894, + "bbox_y1": -11.94543 + } + }, + { + "pk": 142, + "model": "base.region", + "fields": { + "rght": 29, + "code": "MWI", + "name": "Malawi", + "parent": 12, + "level": 3, + "lft": 28, + "tree_id": 90, + "bbox_x0": 32.668991, + "bbox_x1": 35.920441, + "bbox_y0": -17.129459, + "bbox_y1": -9.36468 + } + }, + { + "pk": 143, + "model": "base.region", + "fields": { + "rght": 303, + "code": "MYS", + "name": "Malaysia", + "parent": 7, + "level": 3, + "lft": 302, + "tree_id": 90, + "bbox_x0": 98.935059, + "bbox_x1": 119.448433, + "bbox_y0": 0.66364, + "bbox_y1": 7.58378 + } + }, + { + "pk": 144, + "model": "base.region", + "fields": { + "rght": 285, + "code": "MDV", + "name": "Maldives", + "parent": 9, + "level": 3, + "lft": 284, + "tree_id": 90, + "bbox_x0": 72.616219, + "bbox_x1": 73.76712, + "bbox_y0": -2.90045, + "bbox_y1": 7.11712 + } + }, + { + "pk": 145, + "model": "base.region", + "fields": { + "rght": 111, + "code": "MLI", + "name": "Mali", + "parent": 13, + "level": 3, + "lft": 110, + "tree_id": 90, + "bbox_x0": -12.24261, + "bbox_x1": 4.24495, + "bbox_y0": 10.15951, + "bbox_y1": 25 + } + }, + { + "pk": 146, + "model": "base.region", + "fields": { + "rght": 392, + "code": "MLT", + "name": "Malta", + "parent": 5, + "level": 2, + "lft": 391, + "tree_id": 90, + "bbox_x0": 14.18341, + "bbox_x1": 14.5766, + "bbox_y0": 35.786282, + "bbox_y1": 36.081821 + } + }, + { + "pk": 147, + "model": "base.region", + "fields": { + "rght": 480, + "code": "MHL", + "name": "Marshall Islands", + "parent": 256, + "level": 2, + "lft": 479, + "tree_id": 90, + "bbox_x0": 162.143265, + "bbox_x1": 172.161987, + "bbox_y0": 4.57487, + "bbox_y1": 14.65516 + } + }, + { + "pk": 148, + "model": "base.region", + "fields": { + "rght": 161, + "code": "MTQ", + "name": "Martinique", + "parent": 255, + "level": 3, + "lft": 160, + "tree_id": 90, + "bbox_x0": -61.23011, + "bbox_x1": -60.81551, + "bbox_y0": 14.38244, + "bbox_y1": 14.87881 + } + }, + { + "pk": 149, + "model": "base.region", + "fields": { + "rght": 113, + "code": "MRT", + "name": "Mauritania", + "parent": 13, + "level": 3, + "lft": 112, + "tree_id": 90, + "bbox_x0": -17.066521, + "bbox_x1": -4.8352, + "bbox_y0": 14.71554, + "bbox_y1": 27.298071 + } + }, + { + "pk": 150, + "model": "base.region", + "fields": { + "rght": 31, + "code": "MUS", + "name": "Mauritius", + "parent": 12, + "level": 3, + "lft": 30, + "tree_id": 90, + "bbox_x0": 56.512711, + "bbox_x1": 63.525379, + "bbox_y0": -20.525709, + "bbox_y1": -10.31925 + } + }, + { + "pk": 151, + "model": "base.region", + "fields": { + "rght": 33, + "code": "MYT", + "name": "Mayotte", + "parent": 12, + "level": 3, + "lft": 32, + "tree_id": 90, + "bbox_x0": 45.01461, + "bbox_x1": 45.317131, + "bbox_y0": -13.00045, + "bbox_y1": -12.63383 + } + }, + { + "pk": 152, + "model": "base.region", + "fields": { + "rght": 209, + "code": "MEX", + "name": "Mexico", + "parent": 2, + "level": 3, + "lft": 208, + "tree_id": 90, + "bbox_x0": -118.867172, + "bbox_x1": -86.703392, + "bbox_y0": 14.53285, + "bbox_y1": 32.71862 + } + }, + { + "pk": 153, + "model": "base.region", + "fields": { + "rght": 482, + "code": "FSM", + "name": "Micronesia, Federated States of", + "parent": 256, + "level": 2, + "lft": 481, + "tree_id": 90, + "bbox_x0": 138.052856, + "bbox_x1": 163.034912, + "bbox_y0": 5.25984, + "bbox_y1": 10.02222 + } + }, + { + "pk": 154, + "model": "base.region", + "fields": { + "rght": 394, + "code": "MCO", + "name": "Monaco", + "parent": 5, + "level": 2, + "lft": 393, + "tree_id": 90, + "bbox_x0": 7.4091, + "bbox_x1": 7.43948, + "bbox_y0": 43.724789, + "bbox_y1": 43.7519 + } + }, + { + "pk": 155, + "model": "base.region", + "fields": { + "rght": 273, + "code": "MNG", + "name": "Mongolia", + "parent": 258, + "level": 3, + "lft": 272, + "tree_id": 90, + "bbox_x0": 87.749496, + "bbox_x1": 119.92411, + "bbox_y0": 41.567501, + "bbox_y1": 52.154099 + } + }, + { + "pk": 156, + "model": "base.region", + "fields": { + "rght": 396, + "code": "MNE", + "name": "Montenegro", + "parent": 5, + "level": 2, + "lft": 395, + "tree_id": 90, + "bbox_x0": 18.43705, + "bbox_x1": 20.41608, + "bbox_y0": 41.84808, + "bbox_y1": 43.542912 + } + }, + { + "pk": 157, + "model": "base.region", + "fields": { + "rght": 163, + "code": "MSR", + "name": "Montserrat", + "parent": 255, + "level": 3, + "lft": 162, + "tree_id": 90, + "bbox_x0": -62.24258, + "bbox_x1": -62.14642, + "bbox_y0": 16.671, + "bbox_y1": 16.81732 + } + }, + { + "pk": 158, + "model": "base.region", + "fields": { + "rght": 57, + "code": "MAR", + "name": "Morocco", + "parent": 11, + "level": 3, + "lft": 56, + "tree_id": 90, + "bbox_x0": -11.7805, + "bbox_x1": -1.02441, + "bbox_y0": 26.946489, + "bbox_y1": 35.921909 + } + }, + { + "pk": 159, + "model": "base.region", + "fields": { + "rght": 35, + "code": "MOZ", + "name": "Mozambique", + "parent": 12, + "level": 3, + "lft": 34, + "tree_id": 90, + "bbox_x0": 30.21731, + "bbox_x1": 40.844471, + "bbox_y0": -26.868679, + "bbox_y1": -10.47188 + } + }, + { + "pk": 160, + "model": "base.region", + "fields": { + "rght": 305, + "code": "MMR", + "name": "Myanmar", + "parent": 7, + "level": 3, + "lft": 304, + "tree_id": 90, + "bbox_x0": 92.189209, + "bbox_x1": 101.176788, + "bbox_y0": 9.60035, + "bbox_y1": 28.543249 + } + }, + { + "pk": 161, + "model": "base.region", + "fields": { + "rght": 71, + "code": "NMB", + "name": "Namibia", + "parent": 14, + "level": 3, + "lft": 70, + "tree_id": 90, + "bbox_x0": 11.71563, + "bbox_x1": 25.256701, + "bbox_y0": -28.97142, + "bbox_y1": -16.95989 + } + }, + { + "pk": 162, + "model": "base.region", + "fields": { + "rght": 484, + "code": "NRU", + "name": "Nauru", + "parent": 256, + "level": 2, + "lft": 483, + "tree_id": 90, + "bbox_x0": 166.899017, + "bbox_x1": 166.945267, + "bbox_y0": -0.55232, + "bbox_y1": -0.50429 + } + }, + { + "pk": 163, + "model": "base.region", + "fields": { + "rght": 287, + "code": "NPL", + "name": "Nepal", + "parent": 9, + "level": 3, + "lft": 286, + "tree_id": 90, + "bbox_x0": 80.056221, + "bbox_x1": 88.199318, + "bbox_y0": 26.356501, + "bbox_y1": 30.433001 + } + }, + { + "pk": 164, + "model": "base.region", + "fields": { + "rght": 398, + "code": "NLD", + "name": "Netherlands", + "parent": 5, + "level": 2, + "lft": 397, + "tree_id": 90, + "bbox_x0": 3.35794, + "bbox_x1": 7.2267, + "bbox_y0": 50.750401, + "bbox_y1": 53.554241 + } + }, + { + "pk": 165, + "model": "base.region", + "fields": { + "rght": 165, + "code": "NAN", + "name": "Netherlands Antilles", + "parent": 255, + "level": 3, + "lft": 164, + "tree_id": 90, + "bbox_x0": -69.157188, + "bbox_x1": -62.943661, + "bbox_y0": 11.97318, + "bbox_y1": 18.07024 + } + }, + { + "pk": 166, + "model": "base.region", + "fields": { + "rght": 486, + "code": "NCL", + "name": "New Caledonia", + "parent": 256, + "level": 2, + "lft": 485, + "tree_id": 90, + "bbox_x0": 158.332855, + "bbox_x1": 172.061417, + "bbox_y0": -22.90045, + "bbox_y1": -18.01622 + } + }, + { + "pk": 167, + "model": "base.region", + "fields": { + "rght": 488, + "code": "NZL", + "name": "New Zealand", + "parent": 256, + "level": 2, + "lft": 487, + "tree_id": 90, + "bbox_x0": 165.883804, + "bbox_x1": -175.987198, + "bbox_y0": -52.618591, + "bbox_y1": -29.20997 + } + }, + { + "pk": 168, + "model": "base.region", + "fields": { + "rght": 199, + "code": "NIC", + "name": "Nicaragua", + "parent": 3, + "level": 3, + "lft": 198, + "tree_id": 90, + "bbox_x0": -87.6903, + "bbox_x1": -82.592072, + "bbox_y0": 10.70754, + "bbox_y1": 15.0259 + } + }, + { + "pk": 169, + "model": "base.region", + "fields": { + "rght": 115, + "code": "NER", + "name": "Niger", + "parent": 13, + "level": 3, + "lft": 114, + "tree_id": 90, + "bbox_x0": 0.16625, + "bbox_x1": 15.99564, + "bbox_y0": 11.69697, + "bbox_y1": 23.525021 + } + }, + { + "pk": 170, + "model": "base.region", + "fields": { + "rght": 117, + "code": "NGA", + "name": "Nigeria", + "parent": 13, + "level": 3, + "lft": 116, + "tree_id": 90, + "bbox_x0": 2.66844, + "bbox_x1": 14.68006, + "bbox_y0": 4.27714, + "bbox_y1": 13.892 + } + }, + { + "pk": 171, + "model": "base.region", + "fields": { + "rght": 490, + "code": "NIU", + "name": "Niue", + "parent": 256, + "level": 2, + "lft": 489, + "tree_id": 90, + "bbox_x0": -169.951004, + "bbox_x1": -169.775177, + "bbox_y0": -19.152189, + "bbox_y1": -18.951059 + } + }, + { + "pk": 172, + "model": "base.region", + "fields": { + "rght": 492, + "code": "NFK", + "name": "Norfolk Island", + "parent": 256, + "level": 2, + "lft": 491, + "tree_id": 90, + "bbox_x0": 167.949493, + "bbox_x1": 168.091614, + "bbox_y0": -29.11937, + "bbox_y1": -28.99493 + } + }, + { + "pk": 173, + "model": "base.region", + "fields": { + "rght": 494, + "code": "MNP", + "name": "Northern Mariana Islands", + "parent": 256, + "level": 2, + "lft": 493, + "tree_id": 90, + "bbox_x0": 136.082855, + "bbox_x1": 146.081223, + "bbox_y0": 14.10735, + "bbox_y1": 20.41712 + } + }, + { + "pk": 174, + "model": "base.region", + "fields": { + "rght": 400, + "code": "NOR", + "name": "Norway", + "parent": 5, + "level": 2, + "lft": 399, + "tree_id": 90, + "bbox_x0": 4.43292, + "bbox_x1": 31.168409, + "bbox_y0": 57.962582, + "bbox_y1": 71.185509 + } + }, + { + "pk": 175, + "model": "base.region", + "fields": { + "rght": 450, + "code": "PSE", + "name": "Occupied Palestinian Territory", + "parent": 15, + "level": 2, + "lft": 449, + "tree_id": 90, + "bbox_x0": 34.230461, + "bbox_x1": 35.876499, + "bbox_y0": 31.223499, + "bbox_y1": 33.340099 + } + }, + { + "pk": 176, + "model": "base.region", + "fields": { + "rght": 452, + "code": "OMN", + "name": "Oman", + "parent": 15, + "level": 2, + "lft": 451, + "tree_id": 90, + "bbox_x0": 51.882011, + "bbox_x1": 59.83651, + "bbox_y0": 16.6457, + "bbox_y1": 26.50045 + } + }, + { + "pk": 177, + "model": "base.region", + "fields": { + "rght": 289, + "code": "PAK", + "name": "Pakistan", + "parent": 9, + "level": 3, + "lft": 288, + "tree_id": 90, + "bbox_x0": 60.87859, + "bbox_x1": 77.840813, + "bbox_y0": 23.786699, + "bbox_y1": 37.097 + } + }, + { + "pk": 178, + "model": "base.region", + "fields": { + "rght": 496, + "code": "PLW", + "name": "Palau", + "parent": 256, + "level": 2, + "lft": 495, + "tree_id": 90, + "bbox_x0": 131.169235, + "bbox_x1": 134.723724, + "bbox_y0": 3.00114, + "bbox_y1": 8.09291 + } + }, + { + "pk": 179, + "model": "base.region", + "fields": { + "rght": 201, + "code": "PAN", + "name": "Panama", + "parent": 3, + "level": 3, + "lft": 200, + "tree_id": 90, + "bbox_x0": -83.051453, + "bbox_x1": -77.17411, + "bbox_y0": 7.1979, + "bbox_y1": 9.65045 + } + }, + { + "pk": 180, + "model": "base.region", + "fields": { + "rght": 498, + "code": "PNG", + "name": "Papua New Guinea", + "parent": 256, + "level": 2, + "lft": 497, + "tree_id": 90, + "bbox_x0": 140.842865, + "bbox_x1": 159.48378, + "bbox_y0": -11.65785, + "bbox_y1": -0.86622 + } + }, + { + "pk": 181, + "model": "base.region", + "fields": { + "rght": 233, + "code": "PRY", + "name": "Paraguay", + "parent": 4, + "level": 3, + "lft": 232, + "tree_id": 90, + "bbox_x0": -62.647072, + "bbox_x1": -54.25935, + "bbox_y0": -27.60873, + "bbox_y1": -19.294041 + } + }, + { + "pk": 182, + "model": "base.region", + "fields": { + "rght": 235, + "code": "PER", + "name": "Peru", + "parent": 4, + "level": 3, + "lft": 234, + "tree_id": 90, + "bbox_x0": -81.326736, + "bbox_x1": -68.677979, + "bbox_y0": -18.34972, + "bbox_y1": -0.01297 + } + }, + { + "pk": 183, + "model": "base.region", + "fields": { + "rght": 307, + "code": "PHL", + "name": "Philippines", + "parent": 7, + "level": 3, + "lft": 306, + "tree_id": 90, + "bbox_x0": 116.812721, + "bbox_x1": 126.856628, + "bbox_y0": 4.46811, + "bbox_y1": 21.23415 + } + }, + { + "pk": 184, + "model": "base.region", + "fields": { + "rght": 500, + "code": "PCN", + "name": "Pitcairn", + "parent": 256, + "level": 2, + "lft": 499, + "tree_id": 90, + "bbox_x0": -130.746033, + "bbox_x1": -124.772842, + "bbox_y0": -25.07752, + "bbox_y1": -23.917271 + } + }, + { + "pk": 185, + "model": "base.region", + "fields": { + "rght": 402, + "code": "POL", + "name": "Poland", + "parent": 5, + "level": 2, + "lft": 401, + "tree_id": 90, + "bbox_x0": 14.12281, + "bbox_x1": 24.145781, + "bbox_y0": 49.002022, + "bbox_y1": 54.835812 + } + }, + { + "pk": 186, + "model": "base.region", + "fields": { + "rght": 404, + "code": "PRT", + "name": "Portugal", + "parent": 5, + "level": 2, + "lft": 403, + "tree_id": 90, + "bbox_x0": -31.266001, + "bbox_x1": -6.18931, + "bbox_y0": 30.028061, + "bbox_y1": 42.154121 + } + }, + { + "pk": 187, + "model": "base.region", + "fields": { + "rght": 167, + "code": "PRI", + "name": "Puerto Rico", + "parent": 255, + "level": 3, + "lft": 166, + "tree_id": 90, + "bbox_x0": -67.942719, + "bbox_x1": -65.219978, + "bbox_y0": 17.883039, + "bbox_y1": 18.520161 + } + }, + { + "pk": 188, + "model": "base.region", + "fields": { + "rght": 454, + "code": "QAT", + "name": "Qatar", + "parent": 15, + "level": 2, + "lft": 453, + "tree_id": 90, + "bbox_x0": 50.757011, + "bbox_x1": 52.427509, + "bbox_y0": 24.482901, + "bbox_y1": 26.177509 + } + }, + { + "pk": 189, + "model": "base.region", + "fields": { + "rght": 275, + "code": "KOR", + "name": "Republic of Korea", + "parent": 258, + "level": 3, + "lft": 274, + "tree_id": 90, + "bbox_x0": 124.608147, + "bbox_x1": 130.933899, + "bbox_y0": 33.10611, + "bbox_y1": 38.612301 + } + }, + { + "pk": 190, + "model": "base.region", + "fields": { + "rght": 406, + "code": "MDA", + "name": "Republic of Moldova", + "parent": 5, + "level": 2, + "lft": 405, + "tree_id": 90, + "bbox_x0": 26.618879, + "bbox_x1": 30.16374, + "bbox_y0": 45.468498, + "bbox_y1": 48.490162 + } + }, + { + "pk": 191, + "model": "base.region", + "fields": { + "rght": 37, + "code": "REU", + "name": "Reunion", + "parent": 12, + "level": 3, + "lft": 36, + "tree_id": 90, + "bbox_x0": 55.219082, + "bbox_x1": 55.845039, + "bbox_y0": -21.37221, + "bbox_y1": -20.85685 + } + }, + { + "pk": 192, + "model": "base.region", + "fields": { + "rght": 408, + "code": "ROU", + "name": "Romania", + "parent": 5, + "level": 2, + "lft": 407, + "tree_id": 90, + "bbox_x0": 20.269791, + "bbox_x1": 29.691, + "bbox_y0": 43.626999, + "bbox_y1": 48.266891 + } + }, + { + "pk": 193, + "model": "base.region", + "fields": { + "rght": 410, + "code": "RUS", + "name": "Russian Federation", + "parent": 5, + "level": 2, + "lft": 409, + "tree_id": 90, + "bbox_x0": 19.638861, + "bbox_x1": 180, + "bbox_y0": 41.185902, + "bbox_y1": 81.856903 + } + }, + { + "pk": 194, + "model": "base.region", + "fields": { + "rght": 39, + "code": "RWA", + "name": "Rwanda", + "parent": 12, + "level": 3, + "lft": 38, + "tree_id": 90, + "bbox_x0": 28.8568, + "bbox_x1": 30.89596, + "bbox_y0": -2.84067, + "bbox_y1": -1.05348 + } + }, + { + "pk": 195, + "model": "base.region", + "fields": { + "rght": 73, + "code": "SHN", + "name": "Saint Helena", + "parent": 14, + "level": 3, + "lft": 72, + "tree_id": 90, + "bbox_x0": -14.44153, + "bbox_x1": -5.63286, + "bbox_y0": -40.400452, + "bbox_y1": -7.87757 + } + }, + { + "pk": 196, + "model": "base.region", + "fields": { + "rght": 169, + "code": "KNA", + "name": "Saint Kitts and Nevis", + "parent": 255, + "level": 3, + "lft": 168, + "tree_id": 90, + "bbox_x0": -62.86956, + "bbox_x1": -62.543259, + "bbox_y0": 17.095341, + "bbox_y1": 17.420111 + } + }, + { + "pk": 197, + "model": "base.region", + "fields": { + "rght": 171, + "code": "LCA", + "name": "Saint Lucia", + "parent": 255, + "level": 3, + "lft": 170, + "tree_id": 90, + "bbox_x0": -61.07415, + "bbox_x1": -60.866051, + "bbox_y0": 13.70477, + "bbox_y1": 14.10324 + } + }, + { + "pk": 198, + "model": "base.region", + "fields": { + "rght": 173, + "code": "SPM", + "name": "Saint Pierre and Miquelon", + "parent": 255, + "level": 3, + "lft": 172, + "tree_id": 90, + "bbox_x0": -56.420658, + "bbox_x1": -56.157372, + "bbox_y0": 46.753269, + "bbox_y1": 47.14629 + } + }, + { + "pk": 199, + "model": "base.region", + "fields": { + "rght": 175, + "code": "VCT", + "name": "Saint Vincent and the Grenadines", + "parent": 255, + "level": 3, + "lft": 174, + "tree_id": 90, + "bbox_x0": -61.459251, + "bbox_x1": -61.11388, + "bbox_y0": 12.58101, + "bbox_y1": 13.37783 + } + }, + { + "pk": 200, + "model": "base.region", + "fields": { + "rght": 177, + "code": "BLM", + "name": "Saint-Barthelemy", + "parent": 255, + "level": 3, + "lft": 176, + "tree_id": 90, + "bbox_x0": -62.9338, + "bbox_x1": -62.78286, + "bbox_y0": 17.86622, + "bbox_y1": 17.968599 + } + }, + { + "pk": 201, + "model": "base.region", + "fields": { + "rght": 179, + "code": "MAF", + "name": "Saint-Martin (French part)", + "parent": 255, + "level": 3, + "lft": 178, + "tree_id": 90, + "bbox_x0": -63.15276, + "bbox_x1": -62.972778, + "bbox_y0": 18.052231, + "bbox_y1": 18.13069 + } + }, + { + "pk": 202, + "model": "base.region", + "fields": { + "rght": 502, + "code": "WSM", + "name": "Samoa", + "parent": 256, + "level": 2, + "lft": 501, + "tree_id": 90, + "bbox_x0": -172.798584, + "bbox_x1": -171.33287, + "bbox_y0": -14.06353, + "bbox_y1": -13.4322 + } + }, + { + "pk": 203, + "model": "base.region", + "fields": { + "rght": 412, + "code": "SMR", + "name": "San Marino", + "parent": 5, + "level": 2, + "lft": 411, + "tree_id": 90, + "bbox_x0": 12.40306, + "bbox_x1": 12.51651, + "bbox_y0": 43.893299, + "bbox_y1": 43.992168 + } + }, + { + "pk": 204, + "model": "base.region", + "fields": { + "rght": 119, + "code": "STP", + "name": "Sao Tome and Principe", + "parent": 13, + "level": 3, + "lft": 118, + "tree_id": 90, + "bbox_x0": 5.59955, + "bbox_x1": 7.46637, + "bbox_y0": -0.014, + "bbox_y1": 1.73378 + } + }, + { + "pk": 205, + "model": "base.region", + "fields": { + "rght": 456, + "code": "SAU", + "name": "Saudi Arabia", + "parent": 15, + "level": 2, + "lft": 455, + "tree_id": 90, + "bbox_x0": 34.508282, + "bbox_x1": 55.666672, + "bbox_y0": 16.261169, + "bbox_y1": 32.173481 + } + }, + { + "pk": 206, + "model": "base.region", + "fields": { + "rght": 121, + "code": "SEN", + "name": "Senegal", + "parent": 13, + "level": 3, + "lft": 120, + "tree_id": 90, + "bbox_x0": -17.535231, + "bbox_x1": -11.35588, + "bbox_y0": 12.30727, + "bbox_y1": 16.691629 + } + }, + { + "pk": 207, + "model": "base.region", + "fields": { + "rght": 414, + "code": "SRB", + "name": "Serbia", + "parent": 5, + "level": 2, + "lft": 413, + "tree_id": 90, + "bbox_x0": 18.814581, + "bbox_x1": 23.007, + "bbox_y0": 41.908611, + "bbox_y1": 46.191002 + } + }, + { + "pk": 208, + "model": "base.region", + "fields": { + "rght": 41, + "code": "SYC", + "name": "Seychelles", + "parent": 12, + "level": 3, + "lft": 40, + "tree_id": 90, + "bbox_x0": 46.199211, + "bbox_x1": 56.279499, + "bbox_y0": -10.21712, + "bbox_y1": -3.71151 + } + }, + { + "pk": 209, + "model": "base.region", + "fields": { + "rght": 123, + "code": "SLE", + "name": "Sierra Leone", + "parent": 13, + "level": 3, + "lft": 122, + "tree_id": 90, + "bbox_x0": -13.30763, + "bbox_x1": -10.28423, + "bbox_y0": 6.92868, + "bbox_y1": 10.00043 + } + }, + { + "pk": 210, + "model": "base.region", + "fields": { + "rght": 309, + "code": "SGP", + "name": "Singapore", + "parent": 7, + "level": 3, + "lft": 308, + "tree_id": 90, + "bbox_x0": 103.618248, + "bbox_x1": 104.40847, + "bbox_y0": 1.1158, + "bbox_y1": 1.47062 + } + }, + { + "pk": 211, + "model": "base.region", + "fields": { + "rght": 416, + "code": "SVK", + "name": "Slovakia", + "parent": 5, + "level": 2, + "lft": 415, + "tree_id": 90, + "bbox_x0": 16.833179, + "bbox_x1": 22.570299, + "bbox_y0": 47.728001, + "bbox_y1": 49.603001 + } + }, + { + "pk": 212, + "model": "base.region", + "fields": { + "rght": 418, + "code": "SVN", + "name": "Slovenia", + "parent": 5, + "level": 2, + "lft": 417, + "tree_id": 90, + "bbox_x0": 13.37551, + "bbox_x1": 16.59656, + "bbox_y0": 45.416, + "bbox_y1": 46.87788 + } + }, + { + "pk": 213, + "model": "base.region", + "fields": { + "rght": 504, + "code": "SLB", + "name": "Solomon Islands", + "parent": 256, + "level": 2, + "lft": 503, + "tree_id": 90, + "bbox_x0": 155.508667, + "bbox_x1": 170.200455, + "bbox_y0": -12.2919, + "bbox_y1": -5.16622 + } + }, + { + "pk": 214, + "model": "base.region", + "fields": { + "rght": 43, + "code": "SOM", + "name": "Somalia", + "parent": 12, + "level": 3, + "lft": 42, + "tree_id": 90, + "bbox_x0": 40.988628, + "bbox_x1": 51.413029, + "bbox_y0": -1.66205, + "bbox_y1": 11.9852 + } + }, + { + "pk": 215, + "model": "base.region", + "fields": { + "rght": 75, + "code": "ZAF", + "name": "South Africa", + "parent": 14, + "level": 3, + "lft": 74, + "tree_id": 90, + "bbox_x0": 16.46841, + "bbox_x1": 37.993172, + "bbox_y0": -46.990009, + "bbox_y1": -22.12472 + } + }, + { + "pk": 216, + "model": "base.region", + "fields": { + "rght": 420, + "code": "ESP", + "name": "Spain", + "parent": 5, + "level": 2, + "lft": 419, + "tree_id": 90, + "bbox_x0": -18.160789, + "bbox_x1": 4.32788, + "bbox_y0": 27.63546, + "bbox_y1": 43.789959 + } + }, + { + "pk": 217, + "model": "base.region", + "fields": { + "rght": 291, + "code": "LKA", + "name": "Sri Lanka", + "parent": 9, + "level": 3, + "lft": 290, + "tree_id": 90, + "bbox_x0": 79.516212, + "bbox_x1": 81.88121, + "bbox_y0": 5.9167, + "bbox_y1": 9.8312 + } + }, + { + "pk": 218, + "model": "base.region", + "fields": { + "rght": 59, + "code": "SDN", + "name": "Sudan", + "parent": 11, + "level": 3, + "lft": 58, + "tree_id": 90, + "bbox_x0": 21.83894, + "bbox_x1": 38.833801, + "bbox_y0": 3.48639, + "bbox_y1": 23.146891 + } + }, + { + "pk": 219, + "model": "base.region", + "fields": { + "rght": 237, + "code": "SUR", + "name": "Suriname", + "parent": 4, + "level": 3, + "lft": 236, + "tree_id": 90, + "bbox_x0": -58.086559, + "bbox_x1": -53.977489, + "bbox_y0": 1.83114, + "bbox_y1": 6.00454 + } + }, + { + "pk": 220, + "model": "base.region", + "fields": { + "rght": 422, + "code": "SJM", + "name": "Svalbard and Jan Mayen Islands", + "parent": 5, + "level": 2, + "lft": 421, + "tree_id": 90, + "bbox_x0": -9.07989, + "bbox_x1": 36.815269, + "bbox_y0": 70.82737, + "bbox_y1": 80.834061 + } + }, + { + "pk": 221, + "model": "base.region", + "fields": { + "rght": 77, + "code": "SWZ", + "name": "Swaziland", + "parent": 14, + "level": 3, + "lft": 76, + "tree_id": 90, + "bbox_x0": 30.7941, + "bbox_x1": 32.137272, + "bbox_y0": -27.317101, + "bbox_y1": -25.719641 + } + }, + { + "pk": 222, + "model": "base.region", + "fields": { + "rght": 424, + "code": "SWE", + "name": "Sweden", + "parent": 5, + "level": 2, + "lft": 423, + "tree_id": 90, + "bbox_x0": 10.9661, + "bbox_x1": 24.16634, + "bbox_y0": 55.33696, + "bbox_y1": 69.059937 + } + }, + { + "pk": 223, + "model": "base.region", + "fields": { + "rght": 426, + "code": "CHE", + "name": "Switzerland", + "parent": 5, + "level": 2, + "lft": 425, + "tree_id": 90, + "bbox_x0": 5.95587, + "bbox_x1": 10.49203, + "bbox_y0": 45.81802, + "bbox_y1": 47.80838 + } + }, + { + "pk": 224, + "model": "base.region", + "fields": { + "rght": 458, + "code": "SYR", + "name": "Syrian Arab Republic", + "parent": 15, + "level": 2, + "lft": 457, + "tree_id": 90, + "bbox_x0": 35.727001, + "bbox_x1": 42.384998, + "bbox_y0": 32.3106, + "bbox_y1": 37.319 + } + }, + { + "pk": 225, + "model": "base.region", + "fields": { + "rght": 255, + "code": "TJK", + "name": "Tajikistan", + "parent": 8, + "level": 3, + "lft": 254, + "tree_id": 90, + "bbox_x0": 67.387131, + "bbox_x1": 75.137222, + "bbox_y0": 36.674141, + "bbox_y1": 41.04224 + } + }, + { + "pk": 226, + "model": "base.region", + "fields": { + "rght": 311, + "code": "THA", + "name": "Thailand", + "parent": 7, + "level": 3, + "lft": 310, + "tree_id": 90, + "bbox_x0": 97.343964, + "bbox_x1": 105.636917, + "bbox_y0": 5.61257, + "bbox_y1": 20.464701 + } + }, + { + "pk": 227, + "model": "base.region", + "fields": { + "rght": 313, + "code": "TLS", + "name": "Timor-Leste", + "parent": 7, + "level": 3, + "lft": 312, + "tree_id": 90, + "bbox_x0": 124.075439, + "bbox_x1": 127.345337, + "bbox_y0": -9.51337, + "bbox_y1": -8.13741 + } + }, + { + "pk": 228, + "model": "base.region", + "fields": { + "rght": 125, + "code": "TGO", + "name": "Togo", + "parent": 13, + "level": 3, + "lft": 124, + "tree_id": 90, + "bbox_x0": -0.14731, + "bbox_x1": 1.80669, + "bbox_y0": 6.10441, + "bbox_y1": 11.13897 + } + }, + { + "pk": 229, + "model": "base.region", + "fields": { + "rght": 506, + "code": "TKL", + "name": "Tokelau", + "parent": 256, + "level": 2, + "lft": 505, + "tree_id": 90, + "bbox_x0": -172.517136, + "bbox_x1": -171.182083, + "bbox_y0": -9.43378, + "bbox_y1": -8.53288 + } + }, + { + "pk": 230, + "model": "base.region", + "fields": { + "rght": 508, + "code": "TON", + "name": "Tonga", + "parent": 256, + "level": 2, + "lft": 507, + "tree_id": 90, + "bbox_x0": -176.212646, + "bbox_x1": -173.702438, + "bbox_y0": -22.345711, + "bbox_y1": -15.55326 + } + }, + { + "pk": 231, + "model": "base.region", + "fields": { + "rght": 181, + "code": "TTO", + "name": "Trinidad and Tobago", + "parent": 255, + "level": 3, + "lft": 180, + "tree_id": 90, + "bbox_x0": -61.927391, + "bbox_x1": -60.49144, + "bbox_y0": 10.03648, + "bbox_y1": 11.36118 + } + }, + { + "pk": 232, + "model": "base.region", + "fields": { + "rght": 61, + "code": "TUN", + "name": "Tunisia", + "parent": 11, + "level": 3, + "lft": 60, + "tree_id": 90, + "bbox_x0": 7.52481, + "bbox_x1": 11.59827, + "bbox_y0": 30.240431, + "bbox_y1": 37.56712 + } + }, + { + "pk": 233, + "model": "base.region", + "fields": { + "rght": 428, + "code": "TUR", + "name": "Turkey", + "parent": 5, + "level": 2, + "lft": 427, + "tree_id": 90, + "bbox_x0": 25.664101, + "bbox_x1": 44.8297, + "bbox_y0": 35.809971, + "bbox_y1": 42.105499 + } + }, + { + "pk": 234, + "model": "base.region", + "fields": { + "rght": 257, + "code": "TKM", + "name": "Turkmenistan", + "parent": 8, + "level": 3, + "lft": 256, + "tree_id": 90, + "bbox_x0": 52.441429, + "bbox_x1": 66.684303, + "bbox_y0": 35.14109, + "bbox_y1": 42.795551 + } + }, + { + "pk": 235, + "model": "base.region", + "fields": { + "rght": 183, + "code": "TCA", + "name": "Turks and Caicos Islands", + "parent": 255, + "level": 3, + "lft": 182, + "tree_id": 90, + "bbox_x0": -72.483879, + "bbox_x1": -71.08033, + "bbox_y0": 21.170031, + "bbox_y1": 21.97361 + } + }, + { + "pk": 236, + "model": "base.region", + "fields": { + "rght": 510, + "code": "TUV", + "name": "Tuvalu", + "parent": 256, + "level": 2, + "lft": 509, + "tree_id": 90, + "bbox_x0": 176.06488, + "bbox_x1": 179.883789, + "bbox_y0": -10.75045, + "bbox_y1": -5.64198 + } + }, + { + "pk": 237, + "model": "base.region", + "fields": { + "rght": 45, + "code": "UGA", + "name": "Uganda", + "parent": 12, + "level": 3, + "lft": 44, + "tree_id": 90, + "bbox_x0": 29.573549, + "bbox_x1": 35.001251, + "bbox_y0": -1.47849, + "bbox_y1": 4.23403 + } + }, + { + "pk": 238, + "model": "base.region", + "fields": { + "rght": 430, + "code": "UKR", + "name": "Ukraine", + "parent": 5, + "level": 2, + "lft": 429, + "tree_id": 90, + "bbox_x0": 22.128811, + "bbox_x1": 40.218079, + "bbox_y0": 44.390411, + "bbox_y1": 52.375359 + } + }, + { + "pk": 239, + "model": "base.region", + "fields": { + "rght": 460, + "code": "ARE", + "name": "United Arab Emirates", + "parent": 15, + "level": 2, + "lft": 459, + "tree_id": 90, + "bbox_x0": 51.497978, + "bbox_x1": 56.38343, + "bbox_y0": 22.644409, + "bbox_y1": 26.28219 + } + }, + { + "pk": 240, + "model": "base.region", + "fields": { + "rght": 432, + "code": "GBR", + "name": "United Kingdom", + "parent": 5, + "level": 2, + "lft": 431, + "tree_id": 90, + "bbox_x0": -13.41393, + "bbox_x1": 1.76896, + "bbox_y0": 49.16209, + "bbox_y1": 60.854691 + } + }, + { + "pk": 241, + "model": "base.region", + "fields": { + "rght": 47, + "code": "TZA", + "name": "United Republic of Tanzania", + "parent": 12, + "level": 3, + "lft": 46, + "tree_id": 90, + "bbox_x0": 29.32716, + "bbox_x1": 40.443218, + "bbox_y0": -11.74569, + "bbox_y1": -0.99073 + } + }, + { + "pk": 242, + "model": "base.region", + "fields": { + "rght": 185, + "code": "VIR", + "name": "United States Virgin Islands", + "parent": 255, + "level": 3, + "lft": 184, + "tree_id": 90, + "bbox_x0": -65.086281, + "bbox_x1": -64.56517, + "bbox_y0": 17.681721, + "bbox_y1": 18.458139 + } + }, + { + "pk": 243, + "model": "base.region", + "fields": { + "rght": 211, + "code": "USA", + "name": "United States of America", + "parent": 2, + "level": 3, + "lft": 210, + "tree_id": 90, + "bbox_x0": -179.150558, + "bbox_x1": -66.940643, + "bbox_y0": 18.91172, + "bbox_y1": 71.441048 + } + }, + { + "pk": 244, + "model": "base.region", + "fields": { + "rght": 239, + "code": "URY", + "name": "Uruguay", + "parent": 4, + "level": 3, + "lft": 238, + "tree_id": 90, + "bbox_x0": -58.442719, + "bbox_x1": -53.073929, + "bbox_y0": -35.047939, + "bbox_y1": -30.08222 + } + }, + { + "pk": 245, + "model": "base.region", + "fields": { + "rght": 259, + "code": "UZB", + "name": "Uzbekistan", + "parent": 8, + "level": 3, + "lft": 258, + "tree_id": 90, + "bbox_x0": 55.996632, + "bbox_x1": 73.132271, + "bbox_y0": 37.18433, + "bbox_y1": 45.60519 + } + }, + { + "pk": 246, + "model": "base.region", + "fields": { + "rght": 512, + "code": "VUT", + "name": "Vanuatu", + "parent": 256, + "level": 2, + "lft": 511, + "tree_id": 90, + "bbox_x0": 166.524994, + "bbox_x1": 170.234802, + "bbox_y0": -20.25045, + "bbox_y1": -13.07345 + } + }, + { + "pk": 247, + "model": "base.region", + "fields": { + "rght": 241, + "code": "VEN", + "name": "Venezuela (Bolivarian Republic of)", + "parent": 4, + "level": 3, + "lft": 240, + "tree_id": 90, + "bbox_x0": -73.374313, + "bbox_x1": -59.803768, + "bbox_y0": 0.74368, + "bbox_y1": 12.2019 + } + }, + { + "pk": 248, + "model": "base.region", + "fields": { + "rght": 315, + "code": "VNM", + "name": "Viet Nam", + "parent": 7, + "level": 3, + "lft": 314, + "tree_id": 90, + "bbox_x0": 102.144592, + "bbox_x1": 116.521233, + "bbox_y0": 7.3116, + "bbox_y1": 23.39274 + } + }, + { + "pk": 249, + "model": "base.region", + "fields": { + "rght": 514, + "code": "WLF", + "name": "Wallis and Futuna Islands", + "parent": 256, + "level": 2, + "lft": 513, + "tree_id": 90, + "bbox_x0": -178.206787, + "bbox_x1": -176.08287, + "bbox_y0": -14.38779, + "bbox_y1": -13.17343 + } + }, + { + "pk": 250, + "model": "base.region", + "fields": { + "rght": 63, + "code": "ESH", + "name": "Western Sahara", + "parent": 11, + "level": 3, + "lft": 62, + "tree_id": 90, + "bbox_x0": -17.10317, + "bbox_x1": -8.66942, + "bbox_y0": 20.774151, + "bbox_y1": 28.219179 + } + }, + { + "pk": 251, + "model": "base.region", + "fields": { + "rght": 462, + "code": "YEM", + "name": "Yemen", + "parent": 15, + "level": 2, + "lft": 461, + "tree_id": 90, + "bbox_x0": 41.809608, + "bbox_x1": 54.535992, + "bbox_y0": 12.10717, + "bbox_y1": 19.00276 + } + }, + { + "pk": 252, + "model": "base.region", + "fields": { + "rght": 79, + "code": "ZMB", + "name": "Zambia", + "parent": 14, + "level": 3, + "lft": 78, + "tree_id": 90, + "bbox_x0": 21.99938, + "bbox_x1": 33.705711, + "bbox_y0": -18.07947, + "bbox_y1": -8.22436 + } + }, + { + "pk": 253, + "model": "base.region", + "fields": { + "rght": 81, + "code": "ZWE", + "name": "Zimbabwe", + "parent": 14, + "level": 3, + "lft": 80, + "tree_id": 90, + "bbox_x0": 25.23702, + "bbox_x1": 33.056301, + "bbox_y0": -22.41773, + "bbox_y1": -15.60883 + } + }, + { + "pk": 254, + "model": "base.region", + "fields": { + "rght": 243, + "code": "AME", + "name": "Americas", + "parent": null, + "level": 1, + "lft": 128, + "tree_id": 90, + "bbox_x0": -84.114449, + "bbox_x1": -84.097572, + "bbox_y0": 9.9313, + "bbox_y1": 9.94792 + } + }, + { + "pk": 255, + "model": "base.region", + "fields": { + "rght": 186, + "code": "CRB", + "name": "Caribbean", + "parent": 254, + "level": 2, + "lft": 129, + "tree_id": 90, + "bbox_x0": -85.260178, + "bbox_x1": -59.286541, + "bbox_y0": 10.18548, + "bbox_y1": 27.454559 + } + }, + { + "pk": 256, + "model": "base.region", + "fields": { + "rght": 515, + "code": "PAC", + "name": "Pacific", + "parent": null, + "level": 1, + "lft": 464, + "tree_id": 90, + "bbox_x0": 139.572356, + "bbox_x1": -74.531208, + "bbox_y0": -56.267241, + "bbox_y1": 62.0215969 + } + }, + { + "pk": 257, + "model": "base.region", + "fields": { + "rght": 12, + "code": "CFR", + "name": "Central Africa", + "parent": 10, + "level": 2, + "lft": 3, + "tree_id": 90, + "bbox_x0": -25.35874, + "bbox_x1": 63.525379, + "bbox_y0": -46.900452, + "bbox_y1": 37.56712 + } + }, + { + "pk": 258, + "model": "base.region", + "fields": { + "rght": 276, + "code": "EAS", + "name": "East Asia", + "parent": 6, + "level": 2, + "lft": 261, + "tree_id": 90, + "bbox_x0": 19.6381, + "bbox_x1": 180, + "bbox_y0": -12.56111, + "bbox_y1": 82.50045 + } + }, + { + "pk": 259, + "model": "base.region", + "fields": { + "rght": 59, + "code": "SSD", + "name": "South Sudan", + "parent": 11, + "level": 3, + "lft": 58, + "tree_id": 90, + "bbox_x0": 24.15192, + "bbox_x1": 35.947689, + "bbox_y0": 3.48639, + "bbox_y1": 12.21558 + } + }, + { + "model": "base.menuplaceholder", + "pk": 1, + "fields": { + "name": "TOPBAR_MENU" + } + }, + { + "model": "base.menuplaceholder", + "pk": 2, + "fields": { + "name": "TOPBAR_MENU_LEFT" + } + }, + { + "model": "base.menuplaceholder", + "pk": 3, + "fields": { + "name": "TOPBAR_MENU_RIGHT" + } + }, + { + "model": "base.menuplaceholder", + "pk": 4, + "fields": { + "name": "CARDS_MENU" + } + } +] diff --git a/src/fixtures/sample_admin.json b/src/fixtures/sample_admin.json new file mode 100644 index 0000000..f9a16ec --- /dev/null +++ b/src/fixtures/sample_admin.json @@ -0,0 +1,18 @@ +[{ + "fields": { + "date_joined": "2011-06-09 15:15:27", + "email": "ad@m.in", + "first_name": "", + "groups": [], + "is_active": true, + "is_staff": true, + "is_superuser": true, + "last_login": "2011-06-09 15:45:34", + "last_name": "", + "password": "pbkdf2_sha256$30000$rjuGt0Obn8on$cxF75frIOSaitNklLZ0IJ/VonUW0fwEFVF96o0M+lGc=", + "user_permissions": [], + "username": "admin" + }, + "model": "people.Profile", + "pk": 1000 +}] \ No newline at end of file diff --git a/src/fixtures/sites_template.json b/src/fixtures/sites_template.json new file mode 100644 index 0000000..0e05e86 --- /dev/null +++ b/src/fixtures/sites_template.json @@ -0,0 +1,10 @@ +[ + { + "fields": { + "domain": "example.com", + "name": "example.com" + }, + "model": "sites.site", + "pk": 1 + } +] diff --git a/src/jetty-runner.xml b/src/jetty-runner.xml new file mode 100644 index 0000000..6f56792 --- /dev/null +++ b/src/jetty-runner.xml @@ -0,0 +1,10 @@ + + + + /geoserver + geoserver + + org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern + WONTMATCH + + diff --git a/src/manage.py b/src/manage.py new file mode 100755 index 0000000..5e8a1a7 --- /dev/null +++ b/src/manage.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python + +# -*- coding: utf-8 -*- +######################################################################### +# +# Copyright (C) 2017 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### + +import os +import sys + + +if __name__ == "__main__": + from django.core.management import execute_from_command_line + + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "urban.settings") + execute_from_command_line(sys.argv) diff --git a/src/manage.sh b/src/manage.sh new file mode 100755 index 0000000..6ef686e --- /dev/null +++ b/src/manage.sh @@ -0,0 +1,2 @@ +. $HOME/.override_env +/usr/bin/python /usr/src/urban/manage.py $@ diff --git a/src/manage_dev.sh.sample b/src/manage_dev.sh.sample new file mode 100644 index 0000000..c81b330 --- /dev/null +++ b/src/manage_dev.sh.sample @@ -0,0 +1,3 @@ +set -a +source ../.override_dev_env +python manage.py $@ diff --git a/src/monitoring-cron b/src/monitoring-cron new file mode 100644 index 0000000..51a5a1d --- /dev/null +++ b/src/monitoring-cron @@ -0,0 +1,3 @@ +# */1 * * * * /usr/src/urban/manage.sh collect_metrics -n -t xml >> /var/log/cron.log 2>&1 +# 0 * * * * /usr/src/urban/manage.sh dispatch_metrics >> /var/log/cron.log 2>&1 +# An empty line is required at the end of this file for a valid cron file. diff --git a/src/package/support/geonode.binary b/src/package/support/geonode.binary new file mode 100644 index 0000000..e3bb206 --- /dev/null +++ b/src/package/support/geonode.binary @@ -0,0 +1,15 @@ +#!/bin/bash + +DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE:-geonode.local_settings} + +if [ -n "$WORKON_HOME" ]; then + DJANGO_ADMIN_CMD=$WORKON_HOME/bin/django-admin >/dev/null 2>/dev/null +elif hash django-admin 2>/dev/null; then + DJANGO_ADMIN_CMD=django-admin +fi + +if [ -n "$DJANGO_ADMIN_CMD" ]; then + DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE $DJANGO_ADMIN_CMD $@ +else + DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE django-admin.py $@ +fi diff --git a/src/package/support/geonode.updateip b/src/package/support/geonode.updateip new file mode 100644 index 0000000..827a5c4 --- /dev/null +++ b/src/package/support/geonode.updateip @@ -0,0 +1,213 @@ +#!/bin/bash + +usage () { + bname="$(basename $0)" + ret="$1" + + cat < + substitute SITEURL with , append it + to ALLOWED_HOSTS in local_settings.py + and geoserver printing config lists + This is mandatory and must be the pubic extenral address you use to access GeoNode + $bname <-l|--local> + same as above but taking into account + LOCAL vs PUBLIC ip addresses (or names) + This is optional and if not specified the default is 'localhost' + $bname <-s|--secure> + HTTPS instead of HTTP protocol + $bname <-h|--help> + this help +EOF + + exit $ret +} + +if [[ $# -eq 0 ]] ; then + usage 0 + exit 0 +fi + +POSITIONAL=() +while [[ $# -gt 0 ]] +do +key="$1" + +IS_HTTPS=0 +case $key in + -h|--help) + usage 0 + ;; + -p|--public) + PUBLIC_IP="$2" + shift # past argument + shift # past value + ;; + -l|--local) + LOCAL_IP="$2" + shift # past argument + shift # past value + ;; + -s|--secure) + IS_HTTPS=1 + shift # past argument + ;; + *) # unknown option + POSITIONAL+=("$1") # save it in an array for later + shift # past argument + ;; +esac +done +set -- "${POSITIONAL[@]}" # restore positional parameters + +if [[ $UID != 0 ]]; then + echo "Please run this script with sudo:" + echo "sudo $0 $*" + exit 1 +fi + +if [ -z "$PUBLIC_IP" ]; then + echo "Public IP is mandatory!" + usage 1 +fi + +if [ -z "$LOCAL_IP" ]; then + # LOCAL_IP=$PUBLIC_IP + LOCAL_IP="localhost" +fi + +NET_PROTOCOL="http" +if [[ $IS_HTTPS != 0 ]]; then + NET_PROTOCOL="https" +fi + +# Getting the full netloc +NEW_EXT_IP="$NET_PROTOCOL://$PUBLIC_IP" +NEW_INT_IP="$NET_PROTOCOL://$LOCAL_IP" + +# Removing slash at the end of variables +NEW_EXT_IP=${NEW_EXT_IP%/} +NEW_INT_IP=${NEW_INT_IP%/} + +echo "PUBLIC_IP" $NEW_EXT_IP +echo "LOCAL_IP" $NEW_INT_IP + +GEONODE_ETC=${GEONODE_ETC:-/etc/geonode} +GEOSERVER_DATA_DIR=${GEOSERVER_DATA_DIR:-/usr/share/geoserver/data} +TOMCAT_SERVICE=${TOMCAT_SERVICE:-"invoke-rc.d tomcat8"} +APACHE_SERVICE=${APACHE_SERVICE:-"invoke-rc.d apache2"} + +# Replace SITEURL in $GEONODE_ETC/local_settings.py +echo "Replacing SITEURL value with '$NEW_EXT_IP' in $GEONODE_ETC/local_settings.py ... " | tr -d '\n' +sed -i "s@\(SITEURL[ ]*=[ ]*\).*@\1\'$NEW_EXT_IP\/'@g" $GEONODE_ETC/local_settings.py +echo "done." + +echo "Adding entry for '$PUBLIC_IP' in $GEOSERVER_DATA_DIR/printing/config.yaml ... " | tr -d '\n' +printing_config=$GEOSERVER_DATA_DIR/printing/config.yaml + +if grep -q "$PUBLIC_IP" "$printing_config" +then + echo "'$PUBLIC_IP' already found to the printing whitelist." +else + sed -i "s#hosts:#hosts:\n - !ipMatch\n ip: $PUBLIC_IP#g" $printing_config + echo "done." +fi + +# if ALLOWED_HOSTS already exists ... +# if grep -q "^[ ]*ALLOWED_HOSTS[ ]*=" "$GEONODE_ETC/local_settings.py" +# then +# if [ $IS_REPLACE -eq 1 ] +# then +# echo "Replacing ALLOWED_HOSTS in $GEONODE_ETC/local_settings.py ... " | tr -d '\n' +# sed -i "s/^\([ ]*ALLOWED_HOSTS[ ]*=\).*/\1 [ 'localhost', '$NEWIP', ]/g" "$GEONODE_ETC/local_settings.py" +# echo "done." +# else +# echo "Adding $NEWIP to ALLOWED_HOSTS in $GEONODE_ETC/local_settings.py ... " | tr -d '\n' +# items="$(grep "^[ ]*ALLOWED_HOSTS[ ]*=" "$GEONODE_ETC/local_settings.py" | \ +# sed 's/^[ ]*ALLOWED_HOSTS[ ]*=[ ]*\[//g;s/\][ ]*$//g')" +# already_found=0 +# oldifs="$IFS" +# IFS=',' +# for item in $items +# do +# item_cls="$(echo "$item" | sed "s/^[ ]*['\"]//g;s/['\"][ ]*$//g")" +# if [ "$item_cls" = "$NEWIP" ] +# then +# already_found=1 +# break +# fi +# done +# IFS="$oldifs" +# if [ $already_found -eq 0 ] +# then +# if echo "$items" | grep -q ',[ ]*$' +# then +# items="${items}'$NEWIP', " +# else +# items="${items}, '$NEWIP', " +# fi +# sed -i "s/^\([ ]*ALLOWED_HOSTS[ ]*=\).*/\1 [ $items ]/g" "$GEONODE_ETC/local_settings.py" +# echo "done." +# else +# echo "'$NEWIP' already found in ALLOWED_HOSTS list." +# fi +# fi +# else +# echo "Adding ALLOWED_HOSTS with in $GEONODE_ETC/local_settings.py ... " | tr -d '\n' +# echo "ALLOWED_HOSTS=['localhost', '$NEWIP', ]" >> $GEONODE_ETC/local_settings.py +# echo "done." +# fi + + +# silence the warnings from executing geonode command or they will pollute the commands output +if grep -q "^[ ]*SILENCED_SYSTEM_CHECKS[ ]*=" "$GEONODE_ETC/local_settings.py" +then + true +else + echo "SILENCED_SYSTEM_CHECKS = ['1_8.W001', 'fields.W340']" >> $GEONODE_ETC/local_settings.py +fi + +geonode fixsitename + +echo "Setting up oauth" +# Set oauth keys +oauth_keys=$(geonode fixoauthuri 2>&1) +client_id=`echo $oauth_keys | cut -d \, -f 1` +client_secret=`echo $oauth_keys | cut -d \, -f 2` + +# Updating OAuth2 Service Config +oauth_config="$GEOSERVER_DATA_DIR/security/filter/geonode-oauth2/config.xml" +sed -i "s|.*|$client_id|g" $oauth_config +sed -i "s|.*|$client_secret|g" $oauth_config +sed -i "s|.*|$NEW_INT_IP/o/token/|g" $oauth_config +sed -i "s|.*|$NEW_EXT_IP/o/authorize/|g" $oauth_config +sed -i "s|.*|$NEW_EXT_IP/geoserver/|g" $oauth_config +sed -i "s|.*|$NEW_INT_IP/api/o/v4/tokeninfo/|g" $oauth_config +sed -i "s|.*|$NEW_EXT_IP/account/logout/|g" $oauth_config + +# Updating REST Role Service Config +sed -i "s|.*|$NEW_INT_IP|g" "$GEOSERVER_DATA_DIR/security/role/geonode REST role service/config.xml" + +# Updating GeoServer Global Config +global_config="$GEOSERVER_DATA_DIR/global.xml" +sed -i "s|.*|$NEW_EXT_IP/geoserver|g" $global_config + +# Restart tomcat server +$TOMCAT_SERVICE restart + +echo "Waiting Tomcat Service to Restart..." +sleep 30s + +# Restart apache server +$APACHE_SERVICE restart +echo "Waiting Apache HTTPD Service to Restart..." +sleep 5s + +# Run updatelayers +geonode updatelayers +geonode set_all_datasets_alternate +geonode set_all_datasets_metadata + +# Run updatemaplayerip +geonode updatemaplayerip diff --git a/src/pavement.py b/src/pavement.py new file mode 100644 index 0000000..5221203 --- /dev/null +++ b/src/pavement.py @@ -0,0 +1,1260 @@ +######################################################################### +# +# Copyright (C) 2018 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### + +import fileinput +import glob +import os +import re +import shutil +import subprocess +import signal +import sys +import time +import pytz +import logging +import datetime +from dateutil.parser import parse as parsedate + +from urllib.parse import urlparse +from urllib.request import urlopen, Request + +import zipfile +from tqdm import tqdm +import requests +import math +import psutil + +import yaml +from paver.easy import ( + BuildFailure, + call_task, + cmdopts, + info, + needs, + path, + sh, + task, +) +from setuptools.command import easy_install + +try: + from urban.local_settings import * +except ImportError: + from urban.settings import * + +try: + from paver.path import pushd +except ImportError: + from paver.easy import pushd + +from geonode.settings import ( + on_travis, + core_tests, + internal_apps_tests, + integration_tests, + integration_server_tests, + integration_upload_tests, + integration_monitoring_tests, + integration_csw_tests, + integration_bdd_tests, + INSTALLED_APPS, + GEONODE_CORE_APPS, + GEONODE_INTERNAL_APPS, + GEONODE_APPS, + OGC_SERVER, + ASYNC_SIGNALS, + MONITORING_ENABLED, + CELERY_BEAT_SCHEDULER, +) + +try: + from geonode.settings import TEST_RUNNER_KEEPDB, TEST_RUNNER_PARALLEL + + _keepdb = "--keepdb" if TEST_RUNNER_KEEPDB else "" + _parallel = f"--parallel={TEST_RUNNER_PARALLEL}" if TEST_RUNNER_PARALLEL else "" +except Exception: + _keepdb = "" + _parallel = "" + +assert sys.version_info >= (2, 6), SystemError( + "GeoNode Build requires python 2.6 or better" +) + +dev_config = None +with open("dev_config.yml") as f: + dev_config = yaml.load(f, Loader=yaml.Loader) + + +logger = logging.getLogger(__name__) + + +def grab(src, dest, name): + src, dest, name = map(str, (src, dest, name)) + logger.info(f" src, dest, name --> {src} {dest} {name}") + + if not os.path.exists(dest): + logger.info(f"Downloading {name}") + elif not zipfile.is_zipfile(dest): + logger.info(f"Downloading {name} (corrupt file)") + elif not src.startswith("file://"): + r = requests.head(src) + file_time = datetime.datetime.fromtimestamp(os.path.getmtime(dest)) + url_time = file_time + for _k in ["last-modified", "Date"]: + if _k in r.headers: + url_time = r.headers[_k] + url_date = parsedate(url_time) + utc = pytz.utc + url_date = url_date.replace(tzinfo=utc) + file_time = file_time.replace(tzinfo=utc) + if url_date < file_time: + # Do not download if older than the local one + return + logger.info(f"Downloading updated {name}") + + # Local file does not exist or remote one is newer + if src.startswith("file://"): + src2 = src.replace("file://", "") + if not os.path.exists(src2): + logger.info(f"Source location ({src2}) does not exist") + else: + logger.info(f"Copying local file from {src2}") + shutil.copyfile(src2, dest) + else: + # urlretrieve(str(src), str(dest)) + # Streaming, so we can iterate over the response. + r = requests.get(src, stream=True, timeout=10, verify=False) + # Total size in bytes. + total_size = int(r.headers.get("content-length", 0)) + logger.info(f"Requesting {src}") + block_size = 1024 + wrote = 0 + with open("output.bin", "wb") as f: + for data in tqdm( + r.iter_content(block_size), + total=math.ceil(total_size // block_size), + unit="KB", + unit_scale=False, + ): + wrote += len(data) + f.write(data) + logger.info(f" total_size [{total_size}] / wrote [{wrote}] ") + if total_size != 0 and wrote != total_size: + logger.error( + f"ERROR, something went wrong. Data could not be written. Expected to write {wrote} but wrote {total_size} instead" + ) + else: + shutil.move("output.bin", dest) + try: + # Cleaning up + os.remove("output.bin") + except OSError: + pass + + +@task +@cmdopts( + [ + ("geoserver=", "g", "The location of the geoserver build (.war file)."), + ("jetty=", "j", "The location of the Jetty Runner (.jar file)."), + ("force_exec=", "", "Force GeoServer Setup."), + ] +) +def setup_geoserver(options): + """Prepare a testing instance of GeoServer.""" + # only start if using Geoserver backend + if "geonode.geoserver" not in INSTALLED_APPS: + return + if on_travis and not options.get("force_exec", False): + """Will make use of the docker container for the Integration Tests""" + return + else: + download_dir = path("downloaded") + if not download_dir.exists(): + download_dir.makedirs() + geoserver_dir = path("geoserver") + geoserver_bin = download_dir / os.path.basename( + urlparse(dev_config["GEOSERVER_URL"]).path + ) + jetty_runner = download_dir / os.path.basename( + urlparse(dev_config["JETTY_RUNNER_URL"]).path + ) + geoserver_data = download_dir / os.path.basename( + urlparse(dev_config["DATA_DIR_URL"]).path + ) + grab( + options.get("geoserver", dev_config["GEOSERVER_URL"]), + geoserver_bin, + "geoserver binary", + ) + grab( + options.get("jetty", dev_config["JETTY_RUNNER_URL"]), + jetty_runner, + "jetty runner", + ) + grab( + options.get("geoserver data", dev_config["DATA_DIR_URL"]), + geoserver_data, + "geoserver data-dir", + ) + + if not geoserver_dir.exists(): + geoserver_dir.makedirs() + + webapp_dir = geoserver_dir / "geoserver" + if not webapp_dir: + webapp_dir.makedirs() + + logger.info("extracting geoserver") + z = zipfile.ZipFile(geoserver_bin, "r") + z.extractall(webapp_dir) + + logger.info("extracting geoserver data dir") + z = zipfile.ZipFile(geoserver_data, "r") + z.extractall(geoserver_dir) + + _configure_data_dir() + + +def _configure_data_dir(): + try: + config = path("geoserver/data/global.xml") + with open(config) as f: + xml = f.read() + m = re.search("proxyBaseUrl>([^<]+)", xml) + xml = f"{xml[:m.start(1)]}http://localhost:8080/geoserver{xml[m.end(1):]}" + with open(config, "w") as f: + f.write(xml) + except Exception as e: + print(e) + + try: + config = path("geoserver/data/security/filter/geonode-oauth2/config.xml") + with open(config) as f: + xml = f.read() + m = re.search("accessTokenUri>([^<]+)", xml) + xml = f"{xml[:m.start(1)]}http://localhost:8000/o/token/{xml[m.end(1):]}" + m = re.search("userAuthorizationUri>([^<]+)", xml) + xml = ( + f"{xml[:m.start(1)]}http://localhost:8000/o/authorize/{xml[m.end(1):]}" + ) + m = re.search("redirectUri>([^<]+)", xml) + xml = f"{xml[:m.start(1)]}http://localhost:8080/geoserver/index.html{xml[m.end(1):]}" + m = re.search("checkTokenEndpointUrl>([^<]+)", xml) + xml = f"{xml[:m.start(1)]}http://localhost:8000/api/o/v4/tokeninfo/{xml[m.end(1):]}" + m = re.search("logoutUri>([^<]+)", xml) + xml = f"{xml[:m.start(1)]}http://localhost:8000/account/logout/{xml[m.end(1):]}" + with open(config, "w") as f: + f.write(xml) + except Exception as e: + print(e) + + try: + config = path( + "geoserver/data/security/role/geonode REST role service/config.xml" + ) + with open(config) as f: + xml = f.read() + m = re.search("baseUrl>([^<]+)", xml) + xml = f"{xml[:m.start(1)]}http://localhost:8000{xml[m.end(1):]}" + with open(config, "w") as f: + f.write(xml) + except Exception as e: + print(e) + + +@task +def static(options): + with pushd("geonode/static"): + sh("grunt production") + + +@task +@needs( + [ + "setup_geoserver", + ] +) +def setup(options): + """Get dependencies and prepare a GeoNode development environment.""" + + if MONITORING_ENABLED: + updategeoip(options) + + info( + "GeoNode development environment successfully set up." + "If you have not set up an administrative account," + ' please do so now. Use "paver start" to start up the server.' + ) + + +def grab_winfiles(url, dest, packagename): + # Add headers + headers = {"User-Agent": "Mozilla 5.10"} + request = Request(url, None, headers) + response = urlopen(request) + with open(dest, "wb") as writefile: + writefile.write(response.read()) + + +@task +def win_install_deps(options): + """ + Install all Windows Binary automatically + This can be removed as wheels become available for these packages + """ + download_dir = path("downloaded").abspath() + if not download_dir.exists(): + download_dir.makedirs() + win_packages = { + # required by transifex-client + "Py2exe": dev_config["WINDOWS"]["py2exe"], + # the wheel 1.9.4 installs but pycsw wants 1.9.3, which fails to compile + # when pycsw bumps their pyproj to 1.9.4 this can be removed. + "PyProj": dev_config["WINDOWS"]["pyproj"], + "lXML": dev_config["WINDOWS"]["lxml"], + } + failed = False + for package, url in win_packages.items(): + tempfile = download_dir / os.path.basename(url) + logger.info(f"Installing file ... {tempfile}") + grab_winfiles(url, tempfile, package) + try: + easy_install.main([tempfile]) + except Exception as e: + failed = True + logger.error("install failed with error: ", e) + os.remove(tempfile) + if failed and sys.maxsize > 2**32: + logger.error("64bit architecture is not currently supported") + logger.error("try finding the 64 binaries for py2exe, and pyproj") + elif failed: + logger.error("install failed for py2exe, and/or pyproj") + else: + print( + "Windows dependencies now complete. Run pip install -e geonode --use-mirrors" + ) + + +@task +@cmdopts([("version=", "v", "Legacy GeoNode version of the existing database.")]) +def upgradedb(options): + """ + Add 'fake' data migrations for existing tables from legacy GeoNode versions + """ + version = options.get("version") + if version in {"1.1", "1.2"}: + sh("python -W ignore manage.py migrate maps 0001 --fake") + sh("python -W ignore manage.py migrate avatar 0001 --fake") + elif version is None: + print("Please specify your GeoNode version") + else: + print(f"Upgrades from version {version} are not yet supported.") + + +@task +@cmdopts([("settings=", "s", "Specify custom DJANGO_SETTINGS_MODULE")]) +def updategeoip(options): + """ + Update geoip db + """ + if MONITORING_ENABLED: + settings = options.get("settings", "") + if settings and "DJANGO_SETTINGS_MODULE" not in settings: + settings = f"DJANGO_SETTINGS_MODULE={settings}" + + sh(f"{settings} python -W ignore manage.py updategeoip -o") + + +@task +@cmdopts([("settings=", "s", "Specify custom DJANGO_SETTINGS_MODULE")]) +def sync(options): + """ + Run the migrate and migrate management commands to create and migrate a DB + """ + settings = options.get("settings", "") + if settings and "DJANGO_SETTINGS_MODULE" not in settings: + settings = f"DJANGO_SETTINGS_MODULE={settings}" + + sh(f"{settings} python -W ignore manage.py makemigrations --noinput") + sh(f"{settings} python -W ignore manage.py migrate --noinput") + sh(f"{settings} python -W ignore manage.py loaddata sample_admin.json") + sh(f"{settings} python -W ignore manage.py loaddata default_oauth_apps.json") + sh(f"{settings} python -W ignore manage.py loaddata initial_data.json") + sh(f"{settings} python -W ignore manage.py set_all_datasets_alternate") + sh(f"{settings} python -W ignore manage.py collectstatic --noinput") + + +@task +def package(options): + """ + Creates a tarball to use for building the system elsewhere + """ + import tarfile + import geonode + + version = geonode.get_version() + # Use GeoNode's version for the package name. + pkgname = f"GeoNode-{version}-all" + + # Create the output directory. + out_pkg = path(pkgname) + out_pkg_tar = path(f"{pkgname}.tar.gz") + + # Create a distribution in zip format for the geonode python package. + dist_dir = path("dist") + dist_dir.rmtree() + sh("python setup.py sdist --formats=zip") + + with pushd("package"): + # Delete old tar files in that directory + for f in glob.glob("GeoNode*.tar.gz"): + old_package = path(f) + if old_package != out_pkg_tar: + old_package.remove() + + if out_pkg_tar.exists(): + info(f"There is already a package for version {version}") + return + + # Clean anything that is in the oupout package tree. + out_pkg.rmtree() + out_pkg.makedirs() + + support_folder = path("support") + install_file = path("install.sh") + + # And copy the default files from the package folder. + justcopy(support_folder, out_pkg / "support") + justcopy(install_file, out_pkg) + + geonode_dist = path("..") / "dist" / f"GeoNode-{version}.zip" + justcopy(geonode_dist, out_pkg) + + # Create a tar file with all files in the output package folder. + tar = tarfile.open(out_pkg_tar, "w:gz") + for file in out_pkg.walkfiles(): + tar.add(file) + + # Add the README with the license and important links to documentation. + tar.add("README", arcname=f"{out_pkg}/README.rst") + tar.close() + + # Remove all the files in the temporary output package directory. + out_pkg.rmtree() + + # Report the info about the new package. + info(f"{out_pkg_tar.abspath()} created") + + +@task +@needs(["start_geoserver", "start_django"]) +@cmdopts( + [ + ("bind=", "b", "Bind server to provided IP address and port number."), + ("java_path=", "j", "Full path to java install for Windows"), + ("foreground", "f", "Do not run in background but in foreground"), + ("settings=", "s", "Specify custom DJANGO_SETTINGS_MODULE"), + ], + share_with=["start_django", "start_geoserver"], +) +def start(options): + """ + Start GeoNode (Django, GeoServer & Client) + """ + info("GeoNode is now available.") + + +@task +def stop_django(options): + """ + Stop the GeoNode Django application + """ + if ASYNC_SIGNALS: + kill("python", "celery") + kill("celery", "worker") + kill("python", "runserver") + kill("python", "runmessaging") + + +@task +@cmdopts([("force_exec=", "", "Force GeoServer Stop.")]) +def stop_geoserver(options): + """ + Stop GeoServer + """ + # we use docker-compose for integration tests + if on_travis and not options.get("force_exec", False): + return + + # only start if using Geoserver backend + if "geonode.geoserver" not in INSTALLED_APPS: + return + kill("java", "geoserver") + + # Kill process. + try: + # proc = subprocess.Popen("ps -ef | grep -i -e '[j]ava\|geoserver' | + # awk '{print $2}'", + proc = subprocess.Popen( + "ps -ef | grep -i -e 'geoserver' | awk '{print $2}'", + shell=True, + stdout=subprocess.PIPE, + ) + for pid in map(int, proc.stdout): + info(f"Stopping geoserver (process number {pid})") + os.kill(pid, signal.SIGKILL) + + # Check if the process that we killed is alive. + killed, alive = psutil.wait_procs([psutil.Process(pid=pid)], timeout=30) + for p in alive: + p.kill() + except Exception as e: + info(e) + + +@task +@needs( + [ + "stop_geoserver", + ] +) +def stop(options): + """ + Stop GeoNode + """ + # windows needs to stop the geoserver first b/c we can't tell which python + # is running, so we kill everything + info("Stopping GeoNode ...") + stop_django(options) + + +@task +@cmdopts([("bind=", "b", "Bind server to provided IP address and port number.")]) +def start_django(options): + """ + Start the GeoNode Django application + """ + settings = options.get("settings", "") + if settings and "DJANGO_SETTINGS_MODULE" not in settings: + settings = f"DJANGO_SETTINGS_MODULE={settings}" + bind = options.get("bind", "0.0.0.0:8000") + port = bind.split(":")[1] + foreground = "" if options.get("foreground", False) else "&" + sh(f"{settings} python -W ignore manage.py runserver {bind} {foreground}") + + if ASYNC_SIGNALS: + sh( + f"{settings} celery -A geonode.celery_app:app worker --autoscale=20,10 --without-gossip --without-mingle -Ofair -B -E \ + --statedb=/tmp/worker.state --scheduler={CELERY_BEAT_SCHEDULER} --loglevel=DEBUG \ + --concurrency=10 --max-tasks-per-child=10 -n worker1@%h -f celery.log {foreground}" + ) + sh(f"{settings} python -W ignore manage.py runmessaging {foreground}") + + # wait for Django to start + started = waitfor(f"http://localhost:{port}") + if not started: + info("Django never started properly or timed out.") + sys.exit(1) + + +@task +def start_messaging(options): + """ + Start the GeoNode messaging server + """ + settings = options.get("settings", "") + if settings and "DJANGO_SETTINGS_MODULE" not in settings: + settings = f"DJANGO_SETTINGS_MODULE={settings}" + foreground = "" if options.get("foreground", False) else "&" + sh(f"{settings} python -W ignore manage.py runmessaging {foreground}") + + +@task +@cmdopts( + [ + ("java_path=", "j", "Full path to java install for Windows"), + ("force_exec=", "", "Force GeoServer Start."), + ] +) +def start_geoserver(options): + """ + Start GeoServer with GeoNode extensions + """ + # we use docker-compose for integration tests + if on_travis and not options.get("force_exec", False): + return + + # only start if using Geoserver backend + if "geonode.geoserver" not in INSTALLED_APPS: + return + + GEOSERVER_BASE_URL = OGC_SERVER["default"]["LOCATION"] + url = GEOSERVER_BASE_URL + + if urlparse(GEOSERVER_BASE_URL).hostname != "localhost": + logger.warning( + "Warning: OGC_SERVER['default']['LOCATION'] hostname is not equal to 'localhost'" + ) + + if not GEOSERVER_BASE_URL.endswith("/"): + logger.error("Error: OGC_SERVER['default']['LOCATION'] does not end with a '/'") + sys.exit(1) + + download_dir = path("downloaded").abspath() + jetty_runner = download_dir / os.path.basename(dev_config["JETTY_RUNNER_URL"]) + data_dir = path("geoserver/data").abspath() + geofence_dir = path("geoserver/data/geofence").abspath() + web_app = path("geoserver/geoserver").abspath() + log_file = path("geoserver/jetty.log").abspath() + config = path("scripts/misc/jetty-runner.xml").abspath() + jetty_port = urlparse(GEOSERVER_BASE_URL).port + + import socket + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + socket_free = True + try: + s.bind(("127.0.0.1", jetty_port)) + except OSError as e: + socket_free = False + if e.errno == 98: + info(f"Port {jetty_port} is already in use") + else: + info( + f"Something else raised the socket.error exception while checking port {jetty_port}" + ) + print(e) + finally: + s.close() + + if socket_free: + # @todo - we should not have set workdir to the datadir but a bug in geoserver + # prevents geonode security from initializing correctly otherwise + with pushd(data_dir): + javapath = "java" + if on_travis: + sh( + "sudo apt install -y openjdk-8-jre openjdk-8-jdk;" + " sudo update-java-alternatives --set java-1.8.0-openjdk-amd64;" + ' export JAVA_HOME=$(readlink -f /usr/bin/java | sed "s:bin/java::");' + " export PATH=$JAVA_HOME'bin/java':$PATH;" + ) + # import subprocess + # result = subprocess.run(['update-alternatives', '--list', 'java'], stdout=subprocess.PIPE) + # javapath = result.stdout + javapath = "/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java" + loggernullpath = os.devnull + + # checking if our loggernullpath exists and if not, reset it to + # something manageable + if loggernullpath == "nul": + try: + open("../../downloaded/null.txt", "w+").close() + except OSError: + print( + "Chances are that you have Geoserver currently running. You " + "can either stop all servers with paver stop or start only " + "the django application with paver start_django." + ) + sys.exit(1) + loggernullpath = "../../downloaded/null.txt" + + try: + sh(("%(javapath)s -version") % locals()) + except Exception: + logger.warning( + "Java was not found in your path. Trying some other options: " + ) + javapath_opt = None + if os.environ.get("JAVA_HOME", None): + logger.info("Using the JAVA_HOME environment variable") + javapath_opt = os.path.join( + os.path.abspath(os.environ["JAVA_HOME"]), "bin", "java.exe" + ) + elif options.get("java_path"): + javapath_opt = options.get("java_path") + else: + logger.critical( + "Paver cannot find java in the Windows Environment. " + "Please provide the --java_path flag with your full path to " + "java.exe e.g. --java_path=C:/path/to/java/bin/java.exe" + ) + sys.exit(1) + # if there are spaces + javapath = f'START /B "" "{javapath_opt}"' + + sh( + "%(javapath)s -Xms512m -Xmx2048m -server -Dgwc.context.suffix=gwc -XX:+UseConcMarkSweepGC -XX:MaxPermSize=512m" + " -DGEOSERVER_DATA_DIR=%(data_dir)s" + " -DGEOSERVER_CSRF_DISABLED=true" + " -Dgeofence.dir=%(geofence_dir)s" + " -Djava.awt.headless=true" + # ' -Dgeofence-ovr=geofence-datasource-ovr.properties' + # workaround for JAI sealed jar issue and jetty classloader + # ' -Dorg.eclipse.jetty.server.webapp.parentLoaderPriority=true' + " -jar %(jetty_runner)s" + " --port %(jetty_port)i" + " --log %(log_file)s" + " %(config)s" + " > %(loggernullpath)s &" % locals() + ) + + info(f"Starting GeoServer on {url}") + + # wait for GeoServer to start + started = waitfor(url) + info(f"The logs are available at {log_file}") + + if not started: + # If applications did not start in time we will give the user a chance + # to inspect them and stop them manually. + info( + "GeoServer never started properly or timed out." + "It may still be running in the background." + ) + sys.exit(1) + + +@task +def test(options): + """ + Run GeoNode's Unit Test Suite + """ + if on_travis: + if core_tests: + _apps = GEONODE_CORE_APPS + if internal_apps_tests: + _apps = GEONODE_INTERNAL_APPS + else: + _apps = GEONODE_APPS + + _apps_to_test = [] + for _app in _apps: + if _app and len(_app) > 0 and "geonode" in _app: + _apps_to_test.append(_app) + if ( + MONITORING_ENABLED + and "geonode.monitoring" in INSTALLED_APPS + and "geonode.monitoring" not in _apps_to_test + ): + _apps_to_test.append("geonode.monitoring") + sh( + f"{options.get('prefix')} manage.py test geonode.tests.smoke \ +{('.tests '.join(_apps_to_test))}.tests --noinput {_keepdb} {_parallel}" + ) + + +@task +@cmdopts([("local=", "l", "Set to True if running bdd tests locally")]) +def test_bdd(options): + """ + Run GeoNode's BDD Test Suite + """ + local = str2bool(options.get("local", "false")) + if local: + call_task("reset_hard") + + call_task("setup") + call_task("sync") + if local: + sh("sleep 30") + + info("GeoNode is now available, running the bdd tests now.") + sh("py.test") + + +@task +def test_javascript(options): + with pushd("geonode/static/geonode"): + sh("./run-tests.sh") + + +@task +@cmdopts( + [ + ("name=", "n", "Run specific tests."), + ("settings=", "s", "Specify custom DJANGO_SETTINGS_MODULE"), + ("local=", "l", "Set to True if running bdd tests locally"), + ] +) +def test_integration(options): + """ + Run GeoNode's Integration test suite against the external apps + """ + prefix = options.get("prefix") + local = str2bool(options.get("local", "false")) + if local: + call_task("stop_geoserver") + _reset() + + name = options.get("name", None) + settings = options.get("settings", "") + success = False + try: + call_task("setup", options={"settings": settings, "force_exec": True}) + + if not settings: + settings = "REUSE_DB=1 DJANGO_SETTINGS_MODULE=geonode.settings" + + if name and name in ( + "geonode.tests.csw", + "geonode.tests.integration", + "geonode.geoserver.tests.integration", + ): + call_task("sync", options={"settings": settings}) + if local: + call_task( + "start_geoserver", + options={"settings": settings, "force_exec": True}, + ) + call_task("start", options={"settings": settings}) + if integration_server_tests: + call_task("setup_data", options={"settings": settings}) + elif "geonode.geoserver" in INSTALLED_APPS: + if local: + sh("cp geonode/upload/tests/test_settings.py geonode/") + settings = "geonode.test_settings" + sh( + f"DJANGO_SETTINGS_MODULE={settings} python -W ignore manage.py " + "makemigrations --noinput" + ) + sh( + f"DJANGO_SETTINGS_MODULE={settings} python -W ignore manage.py " + "migrate --noinput" + ) + sh( + f"DJANGO_SETTINGS_MODULE={settings} python -W ignore manage.py " + "loaddata sample_admin.json" + ) + sh( + f"DJANGO_SETTINGS_MODULE={settings} python -W ignore manage.py " + "loaddata geonode/base/fixtures/default_oauth_apps.json" + ) + sh( + f"DJANGO_SETTINGS_MODULE={settings} python -W ignore manage.py " + "loaddata geonode/base/fixtures/initial_data.json" + ) + call_task("start_geoserver") + bind = options.get("bind", "0.0.0.0:8000") + foreground = "" if options.get("foreground", False) else "&" + sh( + f"DJANGO_SETTINGS_MODULE={settings} python -W ignore manage.py runmessaging {foreground}" + ) + sh( + f"DJANGO_SETTINGS_MODULE={settings} python -W ignore manage.py runserver {bind} {foreground}" + ) + sh("sleep 30") + settings = f"REUSE_DB=1 DJANGO_SETTINGS_MODULE={settings}" + else: + call_task("sync", options={"settings": settings}) + + live_server_option = "" + info("Running the tests now...") + sh( + f"{settings} {prefix} manage.py test {name} -v 3 {_keepdb} --noinput {live_server_option}" + ) + + except BuildFailure as e: + info(f"Tests failed! {str(e)}") + else: + success = True + finally: + if local: + stop(options) + _reset() + + if not success: + sys.exit(1) + + +@task +@needs( + [ + "start_geoserver", + ] +) +@cmdopts( + [ + ("coverage", "c", "use this flag to generate coverage during test runs"), + ("local=", "l", "Set to True if running tests locally"), + ] +) +def run_tests(options): + """ + Executes the entire test suite. + """ + if options.get("coverage"): + prefix = 'coverage run --branch --source=geonode \ +--omit="*/__init__*,*/test*,*/wsgi*,*/version*,*/migrations*,\ +*/search_indexes*,*/management/*,*/context_processors*,*/upload/*"' + else: + prefix = "python" + local = options.get("local", "false") # travis uses default to false + + if ( + not integration_tests + and not integration_csw_tests + and not integration_bdd_tests + ): + call_task("test", options={"prefix": prefix}) + elif integration_tests: + if integration_upload_tests: + call_task( + "test_integration", + options={ + "prefix": prefix, + "name": "geonode.upload.tests.integration", + "local": local, + }, + ) + elif integration_monitoring_tests: + call_task( + "test_integration", + options={ + "prefix": prefix, + "name": "geonode.monitoring.tests.integration", + "local": local, + }, + ) + elif integration_csw_tests: + call_task( + "test_integration", + options={"prefix": prefix, "name": "geonode.tests.csw", "local": local}, + ) + elif integration_bdd_tests: + call_task("test_bdd", options={"local": local}) + elif integration_server_tests: + call_task( + "test_integration", + options={ + "prefix": prefix, + "name": "geonode.geoserver.tests.integration", + "local": local, + }, + ) + else: + call_task( + "test_integration", + options={ + "prefix": prefix, + "name": "geonode.tests.integration", + "local": local, + }, + ) + sh("flake8 geonode") + + +@task +@needs(["stop"]) +def reset(options): + """ + Reset a development environment (Database, GeoServer & Catalogue) + """ + _reset() + + +def _reset(): + from geonode import settings + + path = os.path.join(settings.PROJECT_ROOT, "development.db") + sh(f"rm -rf {path}") + sh("rm -rf urban/development.db") + sh("rm -rf urban/uploaded/*") + _configure_data_dir() + + +@needs(["reset"]) +def reset_hard(options): + """ + Reset a development environment (Database, GeoServer & Catalogue) + """ + sh("git clean -dxf") + + +@task +@cmdopts( + [ + ("type=", "t", 'Import specific data type ("vector", "raster", "time")'), + ("settings=", "s", "Specify custom DJANGO_SETTINGS_MODULE"), + ] +) +def setup_data(options): + """ + Import sample data (from gisdata package) into GeoNode + """ + import gisdata + + ctype = options.get("type", None) + + data_dir = gisdata.GOOD_DATA + + if ctype in {"vector", "raster", "time"}: + data_dir = os.path.join(gisdata.GOOD_DATA, ctype) + + settings = options.get("settings", "") + if settings and "DJANGO_SETTINGS_MODULE" not in settings: + settings = f"DJANGO_SETTINGS_MODULE={settings}" + + from geonode import settings as geonode_settings + + if not os.path.exists(geonode_settings.MEDIA_ROOT): + info("media root not available, creating...") + os.makedirs(geonode_settings.MEDIA_ROOT, exist_ok=True) + + sh( + f"{settings} python -W ignore manage.py importlayers -v2 -hh {geonode_settings.SITEURL} {data_dir}" + ) + + +@needs(["package"]) +@cmdopts( + [ + ("key=", "k", "The GPG key to sign the package"), + ("ppa=", "p", "PPA this package should be published to."), + ] +) +def deb(options): + """ + Creates debian packages. + + Example uses: + paver deb + paver deb -k 12345 + paver deb -k 12345 -p geonode/testing + """ + key = options.get("key", None) + ppa = options.get("ppa", None) + + version, simple_version = versions() + + info(f"Creating package for GeoNode version {version}") + + # Get rid of any uncommitted changes to debian/changelog + info("Getting rid of any uncommitted changes in debian/changelog") + sh("git checkout package/debian/changelog") + + # Workaround for git-dch bug + # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=594580 + sh(f"rm -rf {os.path.realpath('package')}/.git") + sh(f"ln -s {os.path.realpath('.git')} {os.path.realpath('package')}") + + with pushd("package"): + # Install requirements + # sh('sudo apt-get -y install debhelper devscripts git-buildpackage') + + # sh(('git-dch --spawn-editor=snapshot --git-author --new-version=%s' + # ' --id-length=6 --ignore-branch --release' % (simple_version))) + # In case you publish from Ubuntu Xenial (git-dch is removed from upstream) + # use the following line instead: + # sh(('gbp dch --spawn-editor=snapshot --git-author --new-version=%s' + # ' --id-length=6 --ignore-branch --release' % (simple_version))) + distribution = "bionic" + # sh(('gbp dch --distribution=%s --force-distribution --spawn-editor=snapshot --git-author --new-version=%s' + # ' --id-length=6 --ignore-branch --release' % (distribution, simple_version))) + + deb_changelog = path("debian") / "changelog" + for idx, line in enumerate(fileinput.input([deb_changelog], inplace=True)): + if idx == 0: + logger.info( + f"geonode ({simple_version}) {distribution}; urgency=high", end="" + ) + else: + print(line.replace("urgency=medium", "urgency=high"), end="") + + # Revert workaround for git-dhc bug + sh("rm -rf .git") + + if key is None and ppa is None: + print("A local installable package") + sh("debuild -uc -us -A") + elif key is None and ppa is not None: + print("A sources package, signed by daemon") + sh("debuild -S") + elif key is not None and ppa is None: + print("A signed installable package") + sh(f"debuild -k{key} -A") + elif key is not None and ppa is not None: + print("A signed, source package") + sh(f"debuild -k{key} -S") + + if ppa is not None: + sh(f"dput ppa:{ppa} geonode_{simple_version}_source.changes") + + +@task +def publish(options): + if "GPG_KEY_GEONODE" in os.environ: + key = os.environ["GPG_KEY_GEONODE"] + else: + print("You need to set the GPG_KEY_GEONODE environment variable") + return + + if "PPA_GEONODE" in os.environ: + ppa = os.environ["PPA_GEONODE"] + else: + ppa = None + + call_task( + "deb", + options={ + "key": key, + "ppa": ppa, + # 'ppa': 'geonode/testing', + # 'ppa': 'geonode/unstable', + }, + ) + + version, simple_version = versions() + if ppa: + sh("git add package/debian/changelog") + sh(f'git commit -m "Updated changelog for version {version}"') + sh(f"git tag -f {version}") + sh(f"git push origin {version}") + sh(f"git tag -f debian/{simple_version}") + sh(f"git push origin debian/{simple_version}") + # sh('git push origin master') + sh("python setup.py sdist upload -r pypi") + + +def versions(): + import geonode + from geonode.version import get_git_changeset + + raw_version = geonode.__version__ + version = geonode.get_version() + timestamp = get_git_changeset() + + major, minor, revision, stage, edition = raw_version + + branch = "dev" + + if stage == "final": + stage = "thefinal" + + if stage == "unstable": + tail = f"{branch}{timestamp}" + else: + tail = f"{stage}{edition}" + + simple_version = f"{major}.{minor}.{revision}+{tail}" + return version, simple_version + + +def kill(arg1, arg2): + """Stops a proces that contains arg1 and is filtered by arg2""" + from subprocess import Popen, PIPE + + # Wait until ready + t0 = time.time() + # Wait no more than these many seconds + time_out = 30 + running = True + + while running and time.time() - t0 < time_out: + if os.name == "nt": + p = Popen( + f'tasklist | find "{arg1}"', + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=False, + ) + else: + p = Popen( + f"ps aux | grep {arg1}", + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + close_fds=True, + ) + + lines = p.stdout.readlines() + + running = False + for line in lines: + # this kills all java.exe and python including self in windows + if (f"{arg2}" in str(line)) or (os.name == "nt" and f"{arg1}" in str(line)): + running = True + + # Get pid + fields = line.strip().split() + + info(f"Stopping {arg1} (process number {int(fields[1])})") + if os.name == "nt": + kill = f'taskkill /F /PID "{int(fields[1])}"' + else: + kill = f"kill -9 {int(fields[1])} 2> /dev/null" + os.system(kill) + + # Give it a little more time + time.sleep(1) + + if running: + _procs = "\n".join([str(_l).strip() for _l in lines]) + raise Exception(f"Could not stop {arg1}: " f"Running processes are\n{_procs}") + + +def waitfor(url, timeout=300): + started = False + for a in range(timeout): + try: + resp = urlopen(url) + except OSError: + pass + else: + if resp.getcode() == 200: + started = True + break + time.sleep(1) + return started + + +def _copytree(src, dst, symlinks=False, ignore=None): + if not os.path.exists(dst): + os.makedirs(dst, exist_ok=True) + for item in os.listdir(src): + s = os.path.join(src, item) + d = os.path.join(dst, item) + if os.path.isdir(s): + try: + shutil.copytree(s, d, symlinks, ignore) + except Exception: + pass + elif os.path.isfile(s): + shutil.copy2(s, d) + + +def justcopy(origin, target): + if os.path.isdir(origin): + shutil.rmtree(target, ignore_errors=True) + _copytree(origin, target) + elif os.path.isfile(origin): + if not os.path.exists(target): + os.makedirs(target, exist_ok=True) + shutil.copy(origin, target) + + +def str2bool(v): + if v and len(v) > 0: + return v.lower() in ("yes", "true", "t", "1") + else: + return False diff --git a/src/paver.sh b/src/paver.sh new file mode 100755 index 0000000..108d651 --- /dev/null +++ b/src/paver.sh @@ -0,0 +1,2 @@ +. $HOME/.override_env +paver $@ diff --git a/src/paver_dev.sh.sample b/src/paver_dev.sh.sample new file mode 100644 index 0000000..718aff7 --- /dev/null +++ b/src/paver_dev.sh.sample @@ -0,0 +1,3 @@ +set -a +source ../.override_dev_env +paver $@ diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..075a55c --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1 @@ +GeoNode~=4.2.3 diff --git a/src/scripts/misc/apache2/geonode.conf.sample b/src/scripts/misc/apache2/geonode.conf.sample new file mode 100644 index 0000000..4f45a02 --- /dev/null +++ b/src/scripts/misc/apache2/geonode.conf.sample @@ -0,0 +1,108 @@ +WSGIDaemonProcess geonode python-path=/home/geo/geonode:/home/geo/Envs/geonode/lib/python2.7/site-packages user=www-data threads=15 processes=2 + + + ServerName http://localhost + ServerAdmin webmaster@localhost + DocumentRoot /home/geo/geonode/geonode + + LimitRequestFieldSize 32760 + LimitRequestLine 32760 + + ErrorLog /var/log/apache2/error.log + LogLevel info + CustomLog /var/log/apache2/access.log combined + + WSGIProcessGroup geonode + WSGIPassAuthorization On + WSGIScriptAlias / /home/geo/geonode/geonode/wsgi.py + + Alias /static/ /home/geo/geonode/geonode/static_root/ + Alias /uploaded/ /home/geo/geonode/geonode/uploaded/ + + + + Order deny,allow + Allow from all + Require all granted + + + Order allow,deny + Options Indexes FollowSymLinks + Allow from all + IndexOptions FancyIndexing + + + + Order allow,deny + Options Indexes FollowSymLinks + Allow from all + Require all granted + IndexOptions FancyIndexing + + + + Order allow,deny + Options Indexes FollowSymLinks + Allow from all + Require all granted + IndexOptions FancyIndexing + + + + Order allow,deny + Options Indexes FollowSymLinks + Allow from all + Require all granted + IndexOptions FancyIndexing + + + + Order allow,deny + Options Indexes FollowSymLinks + Allow from all + Require all granted + IndexOptions FancyIndexing + + + + Order allow,deny + Options Indexes FollowSymLinks + Allow from all + Require all granted + IndexOptions FancyIndexing + + + + Order allow,deny + Options Indexes FollowSymLinks + Allow from all + Require all granted + IndexOptions FancyIndexing + + + + Order allow,deny + Options Indexes FollowSymLinks + Allow from all + Require all granted + IndexOptions FancyIndexing + + + + Order allow,deny + Options Indexes FollowSymLinks + Allow from all + Require all granted + IndexOptions FancyIndexing + + + + Order allow,deny + Allow from all + + + ProxyPreserveHost On + ProxyPass /geoserver http://localhost:8080/geoserver + ProxyPassReverse /geoserver http://localhost:8080/geoserver + + diff --git a/src/scripts/misc/cleanup_pyc.sh b/src/scripts/misc/cleanup_pyc.sh new file mode 100644 index 0000000..4eeae2b --- /dev/null +++ b/src/scripts/misc/cleanup_pyc.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# This is the script directory +# pushd $(dirname $0) + +# This is the current directory +pushd $PWD + +find . -name "*.pyc" -exec rm -f {} \; diff --git a/src/scripts/misc/jetty-runner.xml b/src/scripts/misc/jetty-runner.xml new file mode 100644 index 0000000..c7b747c --- /dev/null +++ b/src/scripts/misc/jetty-runner.xml @@ -0,0 +1,12 @@ + + + + /geoserver + + /WEB-INF/web.xml + false + + org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern + WONTMATCH + + diff --git a/src/setup.py b/src/setup.py new file mode 100644 index 0000000..8d78783 --- /dev/null +++ b/src/setup.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# Copyright (C) 2018 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +import os + +from distutils.core import setup + +from setuptools import find_packages + + +def read(*rnames): + return open(os.path.join(os.path.dirname(__file__), *rnames)).read() + + +setup( + name="urban", + version="4.0.0", + author="", + author_email="", + description="urban, based on GeoNode", + long_description=(read("README.md")), + # Full list of classifiers can be found at: + # http://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + "Development Status :: 1 - Planning", + ], + license="GPL", + keywords="urban geonode django", + url="https://github.com/urban/urban", + packages=find_packages(), + dependency_links=["git+https://github.com/GeoNode/geonode.git#egg=geonode"], + include_package_data=True, +) diff --git a/src/tasks.py b/src/tasks.py new file mode 100644 index 0000000..d58c795 --- /dev/null +++ b/src/tasks.py @@ -0,0 +1,761 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# Copyright (C) 2016 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +import os +import re +import ast +import json +import time +import docker +import socket +import ipaddress +import logging +import datetime + +from urllib.parse import urlparse, urlunparse +from invoke import task + +BOOTSTRAP_IMAGE_CHEIP = "codenvy/che-ip:nightly" + +logger = logging.getLogger(__name__) + + +@task +def waitfordbs(ctx): + print("**************************databases*******************************") + db_host = os.getenv("DATABASE_HOST", "db") + ctx.run(f"/usr/bin/wait-for-databases {db_host}", pty=True) + + +@task +def update(ctx): + print("***************************setting env*********************************") + ctx.run("env", pty=True) + pub_host = _geonode_public_host() + print(f"Public Hostname or IP is {pub_host}") + pub_port = _geonode_public_port() + print(f"Public PORT is {pub_port}") + pub_protocol = "https" if pub_port == "443" else "http" + if pub_protocol == "https" or pub_port == "80": + pub_port = None + db_url = _update_db_connstring() + geodb_url = _update_geodb_connstring() + geonode_docker_host = None + for _cnt in range(1, 29): + try: + geonode_docker_host = str(socket.gethostbyname("geonode")) + except Exception: + print(f"...waiting for NGINX to pop-up...{_cnt}") + time.sleep(1) + + override_env = "$HOME/.override_env" + if os.path.exists(override_env): + os.remove(override_env) + else: + print(f"Can not delete the {override_env} file as it doesn't exists") + + if pub_port: + siteurl = f"{pub_protocol}://{pub_host}:{pub_port}/" + gs_pub_loc = f"http://{pub_host}:{pub_port}/geoserver/" + else: + siteurl = f"{pub_protocol}://{pub_host}/" + gs_pub_loc = f"http://{pub_host}/geoserver/" + envs = { + "local_settings": str(_localsettings()), + "siteurl": os.environ.get("SITEURL", siteurl), + "geonode_docker_host": geonode_docker_host, + "public_protocol": pub_protocol, + "public_fqdn": str(pub_host) + str(f":{pub_port}" if pub_port else ""), + "public_host": str(pub_host), + "dburl": os.environ.get("DATABASE_URL", db_url), + "geodburl": os.environ.get("GEODATABASE_URL", geodb_url), + "static_root": os.environ.get("STATIC_ROOT", "/mnt/volumes/statics/static/"), + "media_root": os.environ.get("MEDIA_ROOT", "/mnt/volumes/statics/uploaded/"), + "geoip_path": os.environ.get("GEOIP_PATH", "/mnt/volumes/statics/geoip.db"), + "monitoring": os.environ.get("MONITORING_ENABLED", False), + "monitoring_host_name": os.environ.get("MONITORING_HOST_NAME", "geonode"), + "monitoring_service_name": os.environ.get( + "MONITORING_SERVICE_NAME", "local-geonode" + ), + "monitoring_data_ttl": os.environ.get("MONITORING_DATA_TTL", 7), + "geonode_geodb_passwd": os.environ.get( + "GEONODE_GEODATABASE_PASSWORD", "geonode_data" + ), + "default_backend_datastore": os.environ.get( + "DEFAULT_BACKEND_DATASTORE", "datastore" + ), + "geonode_db_passwd": os.environ.get("GEONODE_DATABASE_PASSWORD", "geonode"), + "geonode_geodb": os.environ.get("GEONODE_GEODATABASE", "geonode_data"), + "db_url": os.environ.get( + "DATABASE_URL", "postgis://geonode:geonode@db:5432/geonode" + ), + "geodb_url": os.environ.get( + "GEODATABASE_URL", "postgis://geonode:geonode@db:5432/geonode_data" + ), + "geonode_db": os.environ.get("GEONODE_DATABASE", "geonode"), + "gs_loc": os.environ.get( + "GEOSERVER_LOCATION", "http://geoserver:8080/geoserver/" + ), + "gs_web_ui_loc": os.environ.get("GEOSERVER_WEB_UI_LOCATION", gs_pub_loc), + "gs_pub_loc": os.environ.get("GEOSERVER_PUBLIC_LOCATION", gs_pub_loc), + "gs_admin_pwd": os.environ.get("GEOSERVER_ADMIN_PASSWORD", "geoserver"), + "override_fn": override_env, + } + try: + current_allowed = ast.literal_eval( + os.getenv("ALLOWED_HOSTS") + or "['{public_fqdn}', '{public_host}', 'localhost', 'django', 'geonode',]".format( + **envs + ) + ) + except ValueError: + current_allowed = [] + current_allowed.extend([str(pub_host), f"{pub_host}:{pub_port}"]) + allowed_hosts = [str(c) for c in current_allowed] + ['"geonode"', '"django"'] + + ctx.run( + "echo export DJANGO_SETTINGS_MODULE=\ +{local_settings} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export MONITORING_ENABLED=\ +{monitoring} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export MONITORING_HOST_NAME=\ +{monitoring_host_name} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export MONITORING_SERVICE_NAME=\ +{monitoring_service_name} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export MONITORING_DATA_TTL=\ +{monitoring_data_ttl} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export GEOIP_PATH=\ +{geoip_path} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export GEONODE_GEODATABASE_PASSWORD=\ +{geonode_geodb_passwd} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export DEFAULT_BACKEND_DATASTORE=\ +{default_backend_datastore} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export GEONODE_DATABASE_PASSWORD=\ +{geonode_db_passwd} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export GEONODE_GEODATABASE=\ +{geonode_geodb} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export DATABASE_URL=\ +{db_url} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export GEODATABASE_URL=\ +{geodb_url} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export GEONODE_DATABASE=\ +{geonode_db} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export GEOSERVER_LOCATION=\ +{gs_loc} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export GEOSERVER_WEB_UI_LOCATION=\ +{gs_web_ui_loc} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export GEOSERVER_PUBLIC_LOCATION=\ +{gs_pub_loc} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export GEOSERVER_ADMIN_PASSWORD=\ +{gs_admin_pwd} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export SITEURL=\ +{siteurl} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + 'echo export ALLOWED_HOSTS=\ +"\\"{}\\"" >> {override_fn}'.format( + allowed_hosts, **envs + ), + pty=True, + ) + ctx.run( + "echo export DATABASE_URL=\ +{dburl} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export GEODATABASE_URL=\ +{geodburl} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export STATIC_ROOT=\ +{static_root} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export MEDIA_ROOT=\ +{media_root} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export GEOIP_PATH=\ +{geoip_path} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export LOGIN_URL=\ +{siteurl}account/login/ >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export LOGOUT_URL=\ +{siteurl}account/logout/ >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export LOGIN_REDIRECT_URL=\ +{siteurl} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run( + "echo export LOGOUT_REDIRECT_URL=\ +{siteurl} >> {override_fn}".format( + **envs + ), + pty=True, + ) + ctx.run(f"source {override_env}", pty=True) + print("****************************finalize env**********************************") + ctx.run("env", pty=True) + + +@task +def migrations(ctx): + print("**************************migrations*******************************") + ctx.run( + f"python manage.py migrate --noinput --settings={_localsettings()}", pty=True + ) + ctx.run( + f"python manage.py migrate --noinput --settings={_localsettings()} --database=datastore", + pty=True, + ) + try: + ctx.run( + f"python manage.py rebuild_index --noinput --settings={_localsettings()}", + pty=True, + ) + except Exception: + pass + + +@task +def statics(ctx): + print("**************************statics*******************************") + try: + ctx.run("mkdir -p /mnt/volumes/statics/{static,uploads}") + ctx.run( + f"python manage.py collectstatic --noinput --settings={_localsettings()}", + pty=True, + ) + except Exception: + import traceback + + traceback.print_exc() + + +@task +def prepare(ctx): + print("**********************prepare fixture***************************") + ctx.run("rm -rf /tmp/default_oauth_apps_docker.json", pty=True) + _prepare_oauth_fixture() + ctx.run("rm -rf /tmp/default_site.json", pty=True) + _prepare_site_fixture() + + +@task +def fixtures(ctx): + print("**************************fixtures********************************") + ctx.run( + f"python manage.py loaddata sample_admin \ +--settings={_localsettings()}", + pty=True, + ) + ctx.run( + f"python manage.py loaddata /tmp/default_oauth_apps_docker.json \ +--settings={_localsettings()}", + pty=True, + ) + ctx.run( + f"python manage.py loaddata /tmp/default_site.json \ +--settings={_localsettings()}", + pty=True, + ) + ctx.run( + f"python manage.py loaddata initial_data.json \ +--settings={_localsettings()}", + pty=True, + ) + + +@task +def collectstatic(ctx): + print("************************static artifacts******************************") + ctx.run( + f"django-admin.py collectstatic --noinput \ +--settings={_localsettings()}", + pty=True, + ) + + +@task +def monitoringfixture(ctx): + if ast.literal_eval(os.environ.get("MONITORING_ENABLED", "False")): + print("*******************monitoring fixture********************************") + ctx.run("rm -rf /tmp/default_monitoring_apps_docker.json", pty=True) + _prepare_monitoring_fixture() + try: + ctx.run( + f"django-admin loaddata metric_data.json \ + --settings={_localsettings()}", + pty=True, + ) + ctx.run( + f"django-admin loaddata notifications.json \ + --settings={_localsettings()}", + pty=True, + ) + ctx.run( + f"django-admin loaddata /tmp/default_monitoring_apps_docker.json \ + --settings={_localsettings()}", + pty=True, + ) + except Exception as e: + logger.error(f"ERROR installing monitoring fixture: {str(e)}") + + +@task +def updategeoip(ctx): + print("**************************update geoip*******************************") + if ast.literal_eval(os.environ.get("MONITORING_ENABLED", "False")): + ctx.run(f"django-admin.py updategeoip --settings={_localsettings()}", pty=True) + + +@task +def updateadmin(ctx): + print("***********************update admin details**************************") + ctx.run("rm -rf /tmp/django_admin_docker.json", pty=True) + _prepare_admin_fixture( + os.environ.get("ADMIN_PASSWORD", "admin"), + os.environ.get("ADMIN_EMAIL", "admin@example.org"), + ) + ctx.run( + f"django-admin.py loaddata /tmp/django_admin_docker.json \ +--settings={_localsettings()}", + pty=True, + ) + + +@task +def collectmetrics(ctx): + print("************************collect metrics******************************") + ctx.run( + f"python -W ignore manage.py collect_metrics \ +--settings={_localsettings()} -n -t xml", + pty=True, + ) + + +@task +def initialized(ctx): + print("**************************init file********************************") + ctx.run("date > /mnt/volumes/statics/geonode_init.lock") + + +def _docker_host_ip(): + try: + client = docker.from_env(version="1.24") + ip_list = client.containers.run( + BOOTSTRAP_IMAGE_CHEIP, network_mode="host" + ).split("\n") + except Exception: + import traceback + + traceback.print_exc() + ip_list = [ + "127.0.0.1", + ] + if len(ip_list) > 1: + print( + f"Docker daemon is running on more than one \ +address {ip_list}" + ) + print(f"Only the first address:{ip_list[0]} will be returned!") + else: + print( + f"Docker daemon is running at the following \ +address {ip_list[0]}" + ) + return ip_list[0] + +def _is_valid_ip(ip): + try: + ipaddress.IPv4Address(ip) + return True + except Exception as e: + return False + +def _container_exposed_port(component, instname): + port = "80" + try: + client = docker.from_env(version="1.24") + ports_dict = json.dumps( + [ + c.attrs["Config"]["ExposedPorts"] + for c in client.containers.list( + filters={ + "label": f"org.geonode.component={component}", + "status": "running", + } + ) + if str(instname) in c.name + ][0] + ) + for key in json.loads(ports_dict): + port = re.split("/tcp", key)[0] + except Exception: + import traceback + + traceback.print_exc() + return port + + +def _update_db_connstring(): + connstr = os.getenv("DATABASE_URL", None) + if not connstr: + user = os.getenv("GEONODE_DATABASE_USER", "geonode") + pwd = os.getenv("GEONODE_DATABASE_PASSWORD", "geonode") + dbname = os.getenv("GEONODE_DATABASE", "geonode") + dbhost = os.getenv("DATABASE_HOST", "db") + dbport = os.getenv("DATABASE_PORT", 5432) + connstr = f"postgis://{user}:{pwd}@{dbhost}:{dbport}/{dbname}" + return connstr + + +def _update_geodb_connstring(): + geoconnstr = os.getenv("GEODATABASE_URL", None) + if not geoconnstr: + geouser = os.getenv("GEONODE_GEODATABASE_USER", "geonode_data") + geopwd = os.getenv("GEONODE_GEODATABASE_PASSWORD", "geonode_data") + geodbname = os.getenv("GEONODE_GEODATABASE", "geonode_data") + dbhost = os.getenv("DATABASE_HOST", "db") + dbport = os.getenv("DATABASE_PORT", 5432) + geoconnstr = f"postgis://{geouser}:{geopwd}@{dbhost}:{dbport}/{geodbname}" + return geoconnstr + + +def _localsettings(): + settings = os.getenv("DJANGO_SETTINGS_MODULE", "urban.settings") + return settings + + +def _gs_service_availability(url): + import requests + + try: + r = requests.request("get", url, verify=False) + r.raise_for_status() # Raises a HTTPError if the status is 4xx, 5xxx + except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e: + logger.error(f"GeoServer connection error is {e}") + return False + except requests.exceptions.HTTPError as er: + logger.error(f"GeoServer HTTP error is {er}") + return False + else: + logger.info("GeoServer API are available!") + return True + + +def _geonode_public_host(): + gn_pub_hostip = os.getenv("GEONODE_LB_HOST_IP", None) + if not gn_pub_hostip: + gn_pub_hostip = _docker_host_ip() + return gn_pub_hostip + + +def _geonode_public_host_ip(): + gn_pub_hostip = os.getenv("GEONODE_LB_HOST_IP", None) + if not gn_pub_hostip or not _is_valid_ip(gn_pub_hostip): + gn_pub_hostip = _docker_host_ip() + return gn_pub_hostip + + +def _geonode_public_port(): + gn_pub_port = os.getenv("GEONODE_LB_PORT", "") + if not gn_pub_port: + gn_pub_port = _container_exposed_port( + "nginx", os.getenv("GEONODE_INSTANCE_NAME", "geonode") + ) + elif gn_pub_port in ("80", "443"): + gn_pub_port = None + return gn_pub_port + + +def _prepare_oauth_fixture(): + upurl = urlparse(os.environ["SITEURL"]) + default_fixture = [ + { + "model": "oauth2_provider.application", + "pk": 1001, + "fields": { + "skip_authorization": True, + "created": "2018-05-31T10:00:31.661Z", + "updated": "2018-05-31T11:30:31.245Z", + "algorithm": "RS256", + "redirect_uris": f"{urlunparse(upurl)}geoserver/index.html", + "name": "GeoServer", + "authorization_grant_type": "authorization-code", + "client_type": "confidential", + "client_id": str(os.environ["OAUTH2_CLIENT_ID"]), + "client_secret": str(os.environ["OAUTH2_CLIENT_SECRET"]), + "user": ["admin"], + }, + } + ] + with open("/tmp/default_oauth_apps_docker.json", "w") as fixturefile: + json.dump(default_fixture, fixturefile) + + +def _prepare_site_fixture(): + upurl = urlparse(os.environ["SITEURL"]) + default_fixture = [ + { + "model": "sites.site", + "pk": 1, + "fields": {"domain": str(upurl.hostname), "name": str(upurl.hostname)}, + } + ] + with open("/tmp/default_site.json", "w") as fixturefile: + json.dump(default_fixture, fixturefile) + + +def _prepare_monitoring_fixture(): + # upurl = urlparse(os.environ['SITEURL']) + # net_scheme = upurl.scheme + # net_loc = upurl.netloc + pub_ip = _geonode_public_host() + print(f"Public Hostname or IP is {pub_ip}") + pub_port = _geonode_public_port() + print(f"Public PORT is {pub_port}") + try: + geonode_ip = socket.gethostbyname("geonode") + except Exception: + geonode_ip = pub_ip + try: + geoserver_ip = socket.gethostbyname("geoserver") + except Exception: + geoserver_ip = pub_ip + d = "1970-01-01 00:00:00" + default_fixture = [ + { + "fields": { + "active": True, + "ip": str(geonode_ip), + "name": str(os.environ["MONITORING_HOST_NAME"]), + }, + "model": "monitoring.host", + "pk": 1, + }, + { + "fields": {"active": True, "ip": str(geoserver_ip), "name": "geoserver"}, + "model": "monitoring.host", + "pk": 2, + }, + { + "fields": { + "name": str(os.environ["MONITORING_SERVICE_NAME"]), + "url": str(os.environ["SITEURL"]), + "notes": "", + "last_check": d, + "active": True, + "host": 1, + "check_interval": "00:01:00", + "service_type": 1, + }, + "model": "monitoring.service", + "pk": 1, + }, + { + "fields": { + "name": "geoserver-hostgeonode", + "url": str(os.environ["SITEURL"]), + "notes": "", + "last_check": d, + "active": True, + "host": 1, + "check_interval": "00:01:00", + "service_type": 3, + }, + "model": "monitoring.service", + "pk": 2, + }, + { + "fields": { + "name": "geoserver-hostgeoserver", + "url": str(os.environ["GEOSERVER_PUBLIC_LOCATION"]), + "notes": "", + "last_check": d, + "active": True, + "host": 2, + "check_interval": "00:01:00", + "service_type": 4, + }, + "model": "monitoring.service", + "pk": 3, + }, + { + "fields": { + "name": "default-geoserver", + "url": "http://geoserver:8080/geoserver/", + "notes": "", + "last_check": d, + "active": True, + "host": 2, + "check_interval": "00:01:00", + "service_type": 2, + }, + "model": "monitoring.service", + "pk": 4, + }, + ] + with open("/tmp/default_monitoring_apps_docker.json", "w") as fixturefile: + json.dump(default_fixture, fixturefile) + + +def _prepare_admin_fixture(admin_password, admin_email): + from django.contrib.auth.hashers import make_password + + d = datetime.datetime.now() + mdext_date = f"{d.isoformat()[:23]}Z" + default_fixture = [ + { + "fields": { + "date_joined": mdext_date, + "email": admin_email, + "first_name": "", + "groups": [], + "is_active": True, + "is_staff": True, + "is_superuser": True, + "last_login": mdext_date, + "last_name": "", + "password": make_password(admin_password), + "user_permissions": [], + "username": "admin", + }, + "model": "people.Profile", + "pk": 1000, + } + ] + with open("/tmp/django_admin_docker.json", "w") as fixturefile: + json.dump(default_fixture, fixturefile) diff --git a/src/urban/__init__.py b/src/urban/__init__.py new file mode 100644 index 0000000..4f2fc61 --- /dev/null +++ b/src/urban/__init__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# Copyright (C) 2017 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### + +import os + +__version__ = (4, 2, 0, "dev", 0) + + +default_app_config = "urban.apps.AppConfig" + + +def get_version(): + import urban.version + + return urban.version.get_version(__version__) diff --git a/src/urban/apps.py b/src/urban/apps.py new file mode 100644 index 0000000..a5c239e --- /dev/null +++ b/src/urban/apps.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# Copyright (C) 2018 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +import os +from django.apps import AppConfig as BaseAppConfig + + +def run_setup_hooks(*args, **kwargs): + from django.conf import settings + from .celeryapp import app as celeryapp + + LOCAL_ROOT = os.path.abspath(os.path.dirname(__file__)) + settings.TEMPLATES[0]["DIRS"].insert(0, os.path.join(LOCAL_ROOT, "templates")) + + if celeryapp not in settings.INSTALLED_APPS: + settings.INSTALLED_APPS += (celeryapp,) + + +class AppConfig(BaseAppConfig): + name = "urban" + label = "urban" + + def ready(self): + super(AppConfig, self).ready() + run_setup_hooks() diff --git a/src/urban/br/backup.sh b/src/urban/br/backup.sh new file mode 100755 index 0000000..7502e1c --- /dev/null +++ b/src/urban/br/backup.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# ########################################################## +# Run a backup +# SOURCE_URL=$SOURCE_URL TARGET_URL=$TARGET_URL ./urban/br/backup.sh $BKP_FOLDER_NAME +# - BKP_FOLDER_NAME: +# Default value = backup_restore +# Shared Backup Folder name. +# The scripts assume it is located on "root" e.g.: /$BKP_FOLDER_NAME/ +# +# - SOURCE_URL: +# Source Server URL, the one generating the "backup" file. +# +# - TARGET_URL: +# Target Server URL, the one which must be synched. +# +# e.g.: +# docker exec -it django4urban sh -c 'SOURCE_URL=$SOURCE_URL TARGET_URL=$TARGET_URL ./urban/br/backup.sh $BKP_FOLDER_NAME' +# ########################################################## + +# Exit script in case of error +set -e + +echo "-----------------------------------------------------" +echo "STARTING urban BACKUP $(date)" +echo "-----------------------------------------------------" + +if [ "$1" != "" ]; then + BKP_FOLDER_NAME="$1" +else + BKP_FOLDER_NAME="backup_restore" +fi + +cd /usr/src/urban/ + +./manage.sh backup -i -f -c $PWD/urban/br/settings_docker.ini --backup-dir /$BKP_FOLDER_NAME/ + +BKP_FILE_LATEST=$(find /$BKP_FOLDER_NAME/*.zip -type f -exec stat -c '%Y %n' {} \; | sort -nr | awk 'NR==1,NR==1 {print $2}') +BKP_FILE_NAME=$(echo $BKP_FILE_LATEST | tail -n 1 | grep -oP -m 1 "\/$BKP_FOLDER_NAME\/\K.*" | sed 's|.zip||') + +sed -i 's~$~ /'"$BKP_FOLDER_NAME"'/'"$BKP_FILE_NAME"'.zip~g' /$BKP_FOLDER_NAME/$BKP_FILE_NAME.md5 + +echo "-----------------------------------------------------" +cat /$BKP_FOLDER_NAME/$BKP_FILE_NAME.md5 +echo "\n" +echo "-----------------------------------------------------" diff --git a/src/urban/br/restore.sh b/src/urban/br/restore.sh new file mode 100755 index 0000000..47ac9da --- /dev/null +++ b/src/urban/br/restore.sh @@ -0,0 +1,97 @@ +#!/bin/sh +# ########################################################## +# Run a restore +# SOURCE_URL=$SOURCE_URL TARGET_URL=$TARGET_URL ./urban/br/restore.sh $BKP_FOLDER_NAME +# - BKP_FOLDER_NAME: +# Default value = backup_restore +# Shared Backup Folder name. +# The scripts assume it is located on "root" e.g.: /$BKP_FOLDER_NAME/ +# +# - SOURCE_URL: +# Source Server URL, the one generating the "backup" file. +# +# - TARGET_URL: +# Target Server URL, the one which must be synched. +# +# e.g.: +# docker exec -it django4urban sh -c 'SOURCE_URL=$SOURCE_URL TARGET_URL=$TARGET_URL ./urban/br/restore.sh $BKP_FOLDER_NAME' +# ########################################################## + +# Exit script in case of error +set -e + +echo "-----------------------------------------------------" +echo "STARTING urban RESTORE $(date)" +echo "-----------------------------------------------------" + +if [ "$1" != "" ]; then + BKP_FOLDER_NAME="$1" +else + BKP_FOLDER_NAME="backup_restore" +fi + +if [ -z "$SOURCE_URL" ] || [ -z "$TARGET_URL" ] +then + echo "-----------------------------------------------------" + echo "ERROR: SOURCE_URL and TARGET_URL environment variables not set" + echo " e.g.: SOURCE_URL=test.webgis.adbpo.it TARGET_URL=staging.webgis.adbpo.it" + echo "-----------------------------------------------------" + exit 1 +else + echo "$SOURCE_URL --> $TARGET_URL" +fi + +cd /usr/src/urban/ + +echo "-----------------------------------------------------" +echo " 1. BACKUP $TARGET_URL" +echo "-----------------------------------------------------" + +NEW_UUID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) +mkdir /$BKP_FOLDER_NAME/$NEW_UUID/ +SOURCE_URL=$SOURCE_URL TARGET_URL=$TARGET_URL ./urban/br/backup.sh $BKP_FOLDER_NAME/$NEW_UUID + +echo "-----------------------------------------------------" +echo " 2. CHECK BACKUP.md5 $TARGET_URL" +echo "-----------------------------------------------------" + +BKP_FILE_LATEST=$(find /$BKP_FOLDER_NAME/$NEW_UUID/*.zip -type f -exec stat -c '%Y %n' {} \; | sort -nr | awk 'NR==1,NR==1 {print $2}') +BKP_FILE_NAME=$(echo $BKP_FILE_LATEST | tail -n 1 | grep -oP -m 1 "\/$BKP_FOLDER_NAME\/$NEW_UUID\/\K.*" | sed 's|.zip||') + +if md5sum -c /$BKP_FOLDER_NAME/$NEW_UUID/$BKP_FILE_NAME.md5; then + + echo "-----------------------------------------------------" + echo " - Original Backup of $TARGET_URL --> /$BKP_FOLDER_NAME/$NEW_UUID/" + echo " 3. RESTORE FROM $SOURCE_URL" + echo "-----------------------------------------------------" + + RECOVERY_FILE_NAME=$BKP_FILE_NAME + BKP_FILE_LATEST=$(find /$BKP_FOLDER_NAME/*.zip -type f -exec stat -c '%Y %n' {} \; | sort -nr | awk 'NR==1,NR==1 {print $2}') + BKP_FILE_NAME=$(echo $BKP_FILE_LATEST | tail -n 1 | grep -oP -m 1 "\/$BKP_FOLDER_NAME\/\K.*" | sed 's|.zip||') + + if md5sum -c /$BKP_FOLDER_NAME/$BKP_FILE_NAME.md5; then + # The MD5 sum matched + ./manage.sh restore -l -n -f --backup-file /$BKP_FOLDER_NAME/$BKP_FILE_NAME.zip --recovery-file /$BKP_FOLDER_NAME/$NEW_UUID/$RECOVERY_FILE_NAME.zip + ./manage.sh migrate_baseurl -f --source-address=$SOURCE_URL --target-address=$TARGET_URL + ./manage.sh set_all_datasets_metadata -d -i + else + # The MD5 sum didn't match + echo "-----------------------------------------------------" + echo " - Original Backup of $TARGET_URL --> /$BKP_FOLDER_NAME/$NEW_UUID/" + echo "ERROR: The MD5 sum didn't match" + echo "-----------------------------------------------------" + exit 1 + fi +else + # The MD5 sum didn't match + echo "-----------------------------------------------------" + echo " - Original Backup of $TARGET_URL --> /$BKP_FOLDER_NAME/$NEW_UUID/" + echo "ERROR: Could not save $TARGET_URL" + echo "-----------------------------------------------------" + exit 1 +fi + +echo "-----------------------------------------------------" +echo " - Original Backup of $TARGET_URL --> /$BKP_FOLDER_NAME/$NEW_UUID/" +echo "FINISHED urban RESTORE $(date)" +echo "-----------------------------------------------------" diff --git a/src/urban/br/settings_docker.ini b/src/urban/br/settings_docker.ini new file mode 100644 index 0000000..b51f720 --- /dev/null +++ b/src/urban/br/settings_docker.ini @@ -0,0 +1,17 @@ +[database] +pgdump = pg_dump +pgrestore = pg_restore +psql = psql + +[geoserver] +datadir = /geoserver_data/data +# datadir_exclude_file_path = {comma separated list of paths to exclude from geoserver catalog} e.g.: /data,/data/geonode,/geonode +dumpvectordata = yes +dumprasterdata = yes +# data_dt_filter = {cmp_operator} {ISO8601} e.g. > 20019-04-05T24:00 +# data_layername_filter = {comma separated list of layernames, optionally with glob syntax} e.g.: tuscany_*,italy +# data_layername_exclude_filter = {comma separated list of layernames, optionally with glob syntax} e.g.: tuscany_*,italy + +[fixtures] +apps = contenttypes,auth,people,groups,account,guardian,admin,actstream,announcements,avatar,base,documents,geoserver,invitations,pinax_notifications,harvesting,services,layers,maps,oauth2_provider,sites,socialaccount,taggit,tastypie,upload,user_messages,geonode_themes,geoapps,favorite,geonode_client +dumps = contenttypes,auth,people,groups,account,guardian,admin,actstream,announcements,avatar,base,documents,geoserver,invitations,pinax_notifications,harvesting,services,layers,maps,oauth2_provider,sites,socialaccount,taggit,tastypie,upload,user_messages,geonode_themes,geoapps,favorite,geonode_client diff --git a/src/urban/celeryapp.py b/src/urban/celeryapp.py new file mode 100644 index 0000000..d0f3c51 --- /dev/null +++ b/src/urban/celeryapp.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# Copyright (C) 2017 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### + +from __future__ import absolute_import + +import os +from celery import Celery + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "urban.settings") + +app = Celery("urban") + +# Using a string here means the worker will not have to +# pickle the object when using Windows. +app.config_from_object("django.conf:settings", namespace="CELERY") +app.autodiscover_tasks() + + +@app.task(bind=True, name="urban.debug_task", queue="default") +def debug_task(self): + print("Request: {!r}".format(self.request)) diff --git a/src/urban/locale/.gitkeep b/src/urban/locale/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/urban/settings.py b/src/urban/settings.py new file mode 100644 index 0000000..1c448bf --- /dev/null +++ b/src/urban/settings.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# Copyright (C) 2017 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### + +# Django settings for the GeoNode project. +import os +import ast + +try: + from urllib.parse import urlparse, urlunparse + from urllib.request import urlopen, Request +except ImportError: + from urllib2 import urlopen, Request + from urlparse import urlparse, urlunparse +# Load more settings from a file called local_settings.py if it exists +try: + from urban.local_settings import * +# from geonode.local_settings import * +except ImportError: + from geonode.settings import * + +# +# General Django development settings +# +PROJECT_NAME = "urban" + +# add trailing slash to site url. geoserver url will be relative to this +if not SITEURL.endswith("/"): + SITEURL = "{}/".format(SITEURL) + +SITENAME = os.getenv("SITENAME", "urban") + +# Defines the directory that contains the settings file as the LOCAL_ROOT +# It is used for relative settings elsewhere. +LOCAL_ROOT = os.path.abspath(os.path.dirname(__file__)) + +WSGI_APPLICATION = "{}.wsgi.application".format(PROJECT_NAME) + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = os.getenv("LANGUAGE_CODE", "en") + +if PROJECT_NAME not in INSTALLED_APPS: + INSTALLED_APPS += (PROJECT_NAME,) + +# Location of url mappings +ROOT_URLCONF = os.getenv("ROOT_URLCONF", "{}.urls".format(PROJECT_NAME)) + +# Additional directories which hold static files +# - Give priority to local geonode-project ones +STATICFILES_DIRS = [ + os.path.join(LOCAL_ROOT, "static"), +] + STATICFILES_DIRS + +# Location of locale files +LOCALE_PATHS = (os.path.join(LOCAL_ROOT, "locale"),) + LOCALE_PATHS + +TEMPLATES[0]["DIRS"].insert(0, os.path.join(LOCAL_ROOT, "templates")) +loaders = TEMPLATES[0]["OPTIONS"].get("loaders") or [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", +] +# loaders.insert(0, 'apptemplates.Loader') +TEMPLATES[0]["OPTIONS"]["loaders"] = loaders +TEMPLATES[0].pop("APP_DIRS", None) + +LOGGING = { + "version": 1, + "disable_existing_loggers": True, + "formatters": { + "verbose": { + "format": "%(levelname)s %(asctime)s %(module)s %(process)d " + "%(thread)d %(message)s" + }, + "simple": { + "format": "%(message)s", + }, + }, + "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, + "handlers": { + "console": { + "level": "ERROR", + "class": "logging.StreamHandler", + "formatter": "simple", + }, + "mail_admins": { + "level": "ERROR", + "filters": ["require_debug_false"], + "class": "django.utils.log.AdminEmailHandler", + }, + }, + "loggers": { + "django": { + "handlers": ["console"], + "level": "ERROR", + }, + "geonode": { + "handlers": ["console"], + "level": "INFO", + }, + "geoserver-restconfig.catalog": { + "handlers": ["console"], + "level": "ERROR", + }, + "owslib": { + "handlers": ["console"], + "level": "ERROR", + }, + "pycsw": { + "handlers": ["console"], + "level": "ERROR", + }, + "celery": { + "handlers": ["console"], + "level": "DEBUG", + }, + "mapstore2_adapter.plugins.serializers": { + "handlers": ["console"], + "level": "DEBUG", + }, + "geonode_logstash.logstash": { + "handlers": ["console"], + "level": "DEBUG", + }, + }, +} + +CENTRALIZED_DASHBOARD_ENABLED = ast.literal_eval( + os.getenv("CENTRALIZED_DASHBOARD_ENABLED", "False") +) +if ( + CENTRALIZED_DASHBOARD_ENABLED + and USER_ANALYTICS_ENABLED + and "geonode_logstash" not in INSTALLED_APPS +): + INSTALLED_APPS += ("geonode_logstash",) + + CELERY_BEAT_SCHEDULE["dispatch_metrics"] = { + "task": "geonode_logstash.tasks.dispatch_metrics", + "schedule": 3600.0, + } + +LDAP_ENABLED = ast.literal_eval(os.getenv("LDAP_ENABLED", "False")) +if LDAP_ENABLED and "geonode_ldap" not in INSTALLED_APPS: + INSTALLED_APPS += ("geonode_ldap",) + +# Add your specific LDAP configuration after this comment: +# https://docs.geonode.org/en/master/advanced/contrib/#configuration diff --git a/src/urban/static/README b/src/urban/static/README new file mode 100644 index 0000000..548e6b3 --- /dev/null +++ b/src/urban/static/README @@ -0,0 +1,20 @@ +# Introduction + +This directory is used to store static assets for your project. User media files +(FileFields/ImageFields) are not stored here. + +The convention for this directory is: + + * css/ — stores CSS files + * less/ - stores LESS files + * js/ — stores Javascript files + * img/ — stores image files + +# Gulp + +Gulp can be used to automatically build css from LESS files. The gulp process will +watch you LESS files for changes and recompile. To install gulp do +the following 2 steps: + +1. cd into the project directory and install dependencies with `npm install` +2. install gulp command line globally with `sudo npm install -g gulp` diff --git a/src/urban/static/css/site_base.css b/src/urban/static/css/site_base.css new file mode 100644 index 0000000..e69de29 diff --git a/src/urban/static/gulpfile.js b/src/urban/static/gulpfile.js new file mode 100644 index 0000000..a6f9fb5 --- /dev/null +++ b/src/urban/static/gulpfile.js @@ -0,0 +1,23 @@ +var gulp = require('gulp'); +var gutil = require('gulp-util'); +var pkg = require('./package.json'); +var concat = require('gulp-concat'); +var less = require('gulp-less'); +var del = require('del'); + +gulp.task('clean:site_base.css', [], function () { + return del([ './css/site_base.css' ]); +}); + +gulp.task('compile:site_base.css', [], function() { + return gulp.src(["./less/site_base.less"], {base: './'}) + .pipe(less({})) + .pipe(concat("site_base.css")) + .pipe(gulp.dest("./css")); +}); + +gulp.task('watch', function() { + gulp.watch("./less/**/*", ['clean:site_base.css', 'compile:site_base.css']); +}); + +gulp.task('default', ['watch', 'clean:site_base.css', 'compile:site_base.css']); diff --git a/src/urban/static/img/README b/src/urban/static/img/README new file mode 100644 index 0000000..e69de29 diff --git a/src/urban/static/img/bing_aerial_w_labels.png b/src/urban/static/img/bing_aerial_w_labels.png new file mode 100644 index 0000000000000000000000000000000000000000..131c8b8d5c759f6672036a78aae14acdf80180c3 GIT binary patch literal 19491 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ⓈQ=W$OV+xUyo{y)g<@eh4!m9Z{ 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 qchwr;&-tWnpUolm#$wJi?)>G + window.__GEONODE_CONFIG__.overrideLocalConfig = function(localConfig, _) { + // Here the localConfig can be overridden and/or extended + return localConfig; + }; + +{% endblock %} diff --git a/src/urban/urls.py b/src/urban/urls.py new file mode 100644 index 0000000..95be19c --- /dev/null +++ b/src/urban/urls.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# Copyright (C) 2017 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### + +from geonode.urls import urlpatterns + +""" +# You can register your own urlpatterns here +urlpatterns = [ + url(r'^/?$', + homepage, + name='home'), + ] + urlpatterns +""" diff --git a/src/urban/version.py b/src/urban/version.py new file mode 100644 index 0000000..66cb45c --- /dev/null +++ b/src/urban/version.py @@ -0,0 +1,56 @@ +import datetime +import os +import subprocess + + +def get_version(version=None): + "Returns a PEP 386-compliant version number from VERSION." + if version is None: + from geonode import __version__ as version + else: + assert len(version) == 5 + assert version[3] in ("unstable", "beta", "rc", "final") + + # Now build the two parts of the version number: + # main = X.Y[.Z] + # sub = .devN - for pre-alpha releases + # | {a|b|c}N - for alpha, beta and rc releases + + parts = 2 if version[2] == 0 else 3 + main = ".".join(str(x) for x in version[:parts]) + + sub = "" + if version[3] == "unstable": + git_changeset = get_git_changeset() + if git_changeset: + sub = ".dev%s" % git_changeset + + elif version[3] != "final": + mapping = {"beta": "b", "rc": "rc"} + sub = mapping[version[3]] + str(version[4]) + + return main + sub + + +def get_git_changeset(): + """Returns a numeric identifier of the latest git changeset. + + The result is the UTC timestamp of the changeset in YYYYMMDDHHMMSS format. + This value isn't guaranteed to be unique, but collisions are very unlikely, + so it's sufficient for generating the development version numbers. + """ + repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + git_show = subprocess.Popen( + "git show --pretty=format:%ct --quiet HEAD", + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True, + cwd=repo_dir, + universal_newlines=True, + ) + timestamp = git_show.communicate()[0].partition("\n")[0] + try: + timestamp = datetime.datetime.utcfromtimestamp(int(timestamp)) + except ValueError: + return None + return timestamp.strftime("%Y%m%d%H%M%S") diff --git a/src/urban/wsgi.py b/src/urban/wsgi.py new file mode 100644 index 0000000..a269d3d --- /dev/null +++ b/src/urban/wsgi.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +######################################################################### +# +# Copyright (C) 2017 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### + +""" +WSGI config for urban project. + +This module contains the WSGI application used by Django's development server +and any production WSGI deployments. It should expose a module-level variable +named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover +this application via the ``WSGI_APPLICATION`` setting. + +Usually you will have the standard Django WSGI application here, but it also +might make sense to replace the whole Django WSGI application with a custom one +that later delegates to the Django one. For example, you could introduce WSGI +middleware here, or combine a Django application with an application of another +framework. + +""" +import os + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "urban.settings") + +# This application object is used by any WSGI server configured to use this +# file. This includes Django's development server, if the WSGI_APPLICATION +# setting points here. +from django.core.wsgi import get_wsgi_application + +application = get_wsgi_application() + +# Apply WSGI middleware here. +# from helloworld.wsgi import HelloWorldApplication +# application = HelloWorldApplication(application) diff --git a/src/uwsgi.ini b/src/uwsgi.ini new file mode 100644 index 0000000..b74d245 --- /dev/null +++ b/src/uwsgi.ini @@ -0,0 +1,47 @@ +[uwsgi] +# uwsgi-socket = 0.0.0.0:8000 +http-socket = 0.0.0.0:8000 +logto = /var/log/geonode.log +# pidfile = /tmp/geonode.pid + +chdir = /usr/src/urban/ +module = urban.wsgi:application + +strict = false +master = true +enable-threads = true +vacuum = true ; Delete sockets during shutdown +single-interpreter = true +die-on-term = true ; Shutdown when receiving SIGTERM (default is respawn) +need-app = true +thunder-lock = true + +touch-reload = /usr/src/urban/urban/wsgi.py +buffer-size = 32768 + +harakiri = 600 ; forcefully kill workers after 600 seconds +py-callos-afterfork = true ; allow workers to trap signals + +max-requests = 1000 ; Restart workers after this many requests +max-worker-lifetime = 3600 ; Restart workers after this many seconds +reload-on-rss = 2048 ; Restart workers after this much resident memory +worker-reload-mercy = 60 ; How long to wait before forcefully killing workers + +cheaper-algo = busyness +processes = 128 ; Maximum number of workers allowed +cheaper = 8 ; Minimum number of workers allowed +cheaper-initial = 16 ; Workers created at startup +cheaper-overload = 1 ; Length of a cycle in seconds +cheaper-step = 16 ; How many workers to spawn at a time + +cheaper-busyness-multiplier = 30 ; How many cycles to wait before killing workers +cheaper-busyness-min = 20 ; Below this threshold, kill workers (if stable for multiplier cycles) +cheaper-busyness-max = 70 ; Above this threshold, spawn new workers +cheaper-busyness-backlog-alert = 16 ; Spawn emergency workers if more than this many requests are waiting in the queue +cheaper-busyness-backlog-step = 2 ; How many emergency workers to create if there are too many requests in the queue + +# cron = -1 -1 -1 -1 -1 sh -c '/usr/src/urban/manage.sh collect_metrics -n -t xml'; +# cron = 0 0 -1 -1 -1 sh -c 'find /backup_restore/ -type f -mtime +30 -exec rm -f {} \;' +# Remove backup files older than 30 days except the most recent 3 files (a backup is composed by 3 files) +cron = 0 0 -1 -1 -1 sh -c 'find /backup_restore/ -maxdepth 1 -type f -mtime +30 -printf "%T@ %p\n" | sort -n | head -n -3 | awk "{ print $2 }" | xargs -r rm' +cron = 0 0 -1 -1 -1 sh -c 'find /backup_restore/ -type d -ctime +30 -exec rm -rf {} \;' diff --git a/src/wait-for-databases.sh b/src/wait-for-databases.sh new file mode 100755 index 0000000..2f42321 --- /dev/null +++ b/src/wait-for-databases.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +host="$1" +shift + +until PGPASSWORD=${POSTGRES_PASSWORD} psql -h "$host" -U ${POSTGRES_USER} -P "pager=off" -c '\l'; do + >&2 echo "Postgres is unavailable - sleeping" + sleep 1 +done + +until PGPASSWORD=${GEONODE_DATABASE_PASSWORD} psql -h "$host" -U ${GEONODE_DATABASE} -d ${GEONODE_DATABASE} -P "pager=off" -c '\l'; do + >&2 echo "${GEONODE_DATABASE} is unavailable - sleeping" + sleep 1 +done + +until PGPASSWORD=${GEONODE_GEODATABASE_PASSWORD} psql -h "$host" -U ${GEONODE_GEODATABASE} -d ${GEONODE_GEODATABASE} -P "pager=off" -c '\l'; do + >&2 echo "${GEONODE_GEODATABASE} is unavailable - sleeping" + sleep 1 +done + +>&2 echo "GeoNode databases are up - executing command"