GeoSites: GeoNode Multi-Tenancy

GeoSites is a way to run multiple websites with a single instance of GeoNode. Each GeoSite can have different templates, apps, and data permissions but share a single database (useful for sharing users and data layers), GeoServer, and CSW. This is useful when multiple websites are desired to support different sets of users, but with a similar set of data and overall look and feel of the sites. Users can be given permission to access multiple sites if needed, which also allows administrative groups can be set up to support all sites with one account.

Master Website

A GeoSites installation uses a ‘master’ GeoNode website that has some additional administrative pages for doing data management. Layers, Maps, Documents, Users, and Groups can all be added and removed from different sites. Users can be given access to any number of sites, and data may appear on only a single site, or all of them. Additionally, if desired, any or all of the Django apps installed on the other sites can be added to the master site to provide a single administrative interface that gives full access to all apps. The master site need not be accessible from the outside so that it can be used as an internal tool to the organization.

Users created on a particular site are created with access to just that site. Data uploaded to a particular site is given permission on that site as well as the master site. Any further adjustments to site-based permissions must be done from the master site.

Database

The master site, and all of the individual GeoSites, share a single database. Objects, including users, groups, and data layers, all appear within the database but an additional sites table indicates which objects have access to which sites. The geospatial data served by GeoServer (e.g., from PostGIS) can exist in the database like normal, since GeoServer will authenticate against GeoNode, which will use it’s database to determine permissions based on the object, current user, and site.

GeoServer

A single GeoServer instance is used to serve data to all of the GeoSites. Data that is common to all sites can be added to the master site which will appear in the generic ‘geonode’ workspace.

Settings Files and Templates

A key component in managing multiple sites is keeping data organized and using a structured series of settings files so that common settings can be shared and only site specific settings are separated out. It is also best to import the default GeoNode settings from the GeoNode installation. This prevents the settings from having to be manually upgraded if there is any default change the GeoNode settings.

Settings which are common to all GeoSites, but differ from the default GeoNode, are separated into a contrib/geosites/settings.py file. Then, each individual site has settings file which imports from the master site and will then only need to specify a small selection that make that site unique, such as:

  • SITE_ID: Each one is unique, the master site should have a SITE_ID of 1.
  • SITENAME
  • SITEURL
  • ROOT_URLCONF: This may be optional. The master site url.conf can be configured to automatically import the urls.py of all SITE_APPS, so a different ROOT_URLCONF is only needed if there are further differences.
  • SITE_APPS: Containing the site specific apps
  • App settings: Any further settings required for the above sites
  • Other site specific settings, such as REGISTRATION_OPEN

A GeoSite therefore has four layers of imports, which is used for settings as well as the search path for templates. First it uses the individual site files, then the master GeoSite, then geosites contrib app, then default GeoNode. These are specified via variables defined in settings:

  • SITE_ROOT: The directory where the site specific settings and files are located (templates, static)
  • PROJECT_ROOT: The top-level directory of all the GeoSites which should include the global settings file as well as template and static files
  • GEOSITES_ROOT: Geosites app directory.
  • GEONODE_ROOT: The GeoNode directory.

The TEMPLATE_DIRS, and STATICFILES_DIRS will then include all three directories as shown:

TEMPLATE_DIRS = (
    os.path.join(SITE_ROOT, 'templates/'),
    os.path.join(PROJECT_ROOT, 'templates/'),  # files common to all sites
    os.path.join(GEOSITES_ROOT, 'templates/'),
    os.path.join(GEONODE_ROOT, 'templates/')
)

STATICFILES_DIRS = (
    os.path.join(SITE_ROOT, 'static/'),
    os.path.join(PROJECT_ROOT, 'static/'),
    os.path.join(GEONODE_ROOT, 'static/')
)

At the end of the post_settings.py the following variables will be set based on site specific settings:

# Login and logout urls override
LOGIN_URL = os.getenv('LOGIN_URL', urljoin(SITEURL, 'account/login/'))
LOGOUT_URL = os.getenv('LOGOUT_URL', urljoin(SITEURL, 'account/logout/'))

ACCOUNT_LOGIN_REDIRECT_URL = os.getenv('LOGIN_REDIRECT_URL', SITEURL)
ACCOUNT_LOGOUT_REDIRECT_URL =  os.getenv('LOGOUT_REDIRECT_URL', SITEURL)


OGC_SERVER['default']['location'] = GEOSERVER_LOCATION
OGC_SERVER['default']['WEB_UI_LOCATION'] = GEOSERVER_WEB_UI_LOCATION
OGC_SERVER['default']['PUBLIC_LOCATION'] = GEOSERVER_PUBLIC_LOCATION
OGC_SERVER['default']['USER'] = OGC_SERVER_DEFAULT_USER
OGC_SERVER['default']['PASSWORD'] = OGC_SERVER_DEFAULT_PASSWORD
OGC_SERVER['default']['DATASTORE'] = 'datastore'
CATALOGUE['default']['URL'] = urljoin(SITEURL, '/catalogue/csw')
PYCSW['CONFIGURATION']['server']['url'] = CATALOGUE['default']['URL']
PYCSW['CONFIGURATION']['metadata:main']['provider_url'] = SITEURL

if USE_GEOSERVER:
    LOCAL_GEOSERVER['source']['url'] = OGC_SERVER['default']['PUBLIC_LOCATION'] + "wms"
    PUBLIC_GEOSERVER['source']['url'] = OGC_SERVER['default']['PUBLIC_LOCATION'] + "ows"
    baselayers = MAP_BASELAYERS
    MAP_BASELAYERS = [PUBLIC_GEOSERVER]
    MAP_BASELAYERS.extend(baselayers)

# Update databases if site has own database
if SITE_DATABASES:
    DATABASES.update(SITE_DATABASES)

# Update apps if site has own apps
if SITE_APPS:
    INSTALLED_APPS += SITE_APPS

# Put static files in root
STATIC_ROOT = os.path.join(SERVE_PATH, 'static')

# Put media files in root
MEDIA_ROOT = os.path.join(SERVE_PATH, 'uploaded')

# add datastore if defined
if DATASTORE in DATABASES.keys():
    OGC_SERVER['default']['DATASTORE'] = DATASTORE

Templates and Static Files

As mentioned above for each website there will be four directories used for template and static files. The first template file found will be the one used so templates in the SITE_ROOT/templates directory will override those in PROJECT_ROOT/templates, which will override those in GEOSITES_ROOT/templates, which will override those in GEONODE_ROOT/templates.

Static files work differently because (at least on a production server) they are collected and stored in a single location. Because of this care must be taken to avoid clobbering of files between sites, so each site directory should contain all static files in a subdirectory with the name of the site (e.g., static_root/siteA/logo.png )

The location of the proper static directory can then be found in the templates syntax such as:

{{ STATIC_URL }}{{ SITENAME|lower }}/logo.png

Permissions by Site

By default GeoNode is publicly available. In the case of GeoSites, new data will be publicly available, but only for the site it was added to, and the master site (all data is added to the master site).

Activate geosites app

In order to use geonode in Multi-tenancy mode, follow these steps: 1. check in settings.py if ‘geonode.contrib.geosites’ in GEONODE_CONTRIB_APPS is rightly uncommented 2. add in settings.py ‘geonode.contrib.geosites’ in INSTALLED_APPS 3. run python manage.py syncdb

Adding New Sites

To add a new site follow the following steps:

  1. copy the directory site_template in your geonode-project folder and give it a name
  2. from geonode administration pannel add ‘new site’
  3. create a virtualhost in the webserver related to the new created site. Remember to setup the WSGIDeamonProcess with the name you gave to the folder created at point 1. and the path to the geosites directory. WSGIProcessGroup have to be pointed to the name you choose for the folder you created at point 1. Eventually, WSGIScriptAlias have to be set to the wsgi.py you have in your site folder.
  4. check the configuration files: local_settings.py, pre_settings.py, post_settings.py in /geonode-project as well as local_settings.py and settings.py in your site folder:
    • in /geonode-project/local_settings.py set the variable SERVE_PATH. It has to point geosites folder.
    • in the local_setting of the site folder insert the values to the following variables:
      • SITE_ID
      • SITE_NAME
      • SITE_URL
  5. create static_root directory where you usually let the webserver serve webpages (e.g., /var/www ) and give it grants to be accessed by the user www-data
  6. create an uploaded/layers and uploaded/thumbs folder in your geonode-project folder and give them grants as follow:
    sudo mkdir -p geonode-project/uploaded/thumbs sudo mkdir -p geonode-project/uploaded/layers sudo chmod -Rf 755 geonode-project/uploaded/thumbs sudo chmod -Rf 755 geonode-project/uploaded/layers
  7. run python manage.py collectstatics - Pay attention on the folder where you are running the command and the folder structure of your geonode/geosites project, in case pass to the path the settings file by using –settings to the python command
  8. you can customize the look and feel of your site working on the css and html file you find in your template site directory. After a change, run again collectstatics command.