Customize the application¶
The application can be customized with the following mechanisms: functionalities, UI metadata and data model extensions.
The functionalities will be attached to the role and the theme,
and the UI metadata will be attached to all the elements of the theme.
They should be configured in the vars file, in the admin_interface /
available_functionalities or respectively available_metadata.
It is a list of objects which have a name and a type.
The type can be:
stringlista list of stringsbooleanintegerfloatjsondatetimedatetimesee the python-dateutil documentationurlsee below
Check CONST_vars.yaml for examples of usage.
In order to inherit the default values from CONST_vars.yaml, make sure the update_paths section contains
the item admin_interface.available_functionalities or respectively admin_interface.available_metadata.
URL¶
In the admin interface, we can use for all URL definitions the following special schema:
static: to use a static route,static:///icon.pngwill get the URL of thestaticstatic route of the project.static://custom-static/icon.pngwill get the URL of thecustom-staticstatic route of the project.static://prj:img/icon.pngwill get the URL of theimgstatic route ofprj.
config: to get the server name from the URL, with the config from thevarsfile:servers: my_server: https://example.com/test
config://my_server/icon.pngwill be transformed into the URLhttps://example.com/test/icon.png.
Functionalities¶
c2cgeoportal provides the concept of functionality that can be used to customize the application according to the user’s permissions.
A functionality may be associated to users through different ways:
- Functionalities for anonymous users are defined through the
functionalities:anonymousvariable in thevars.yamlconfiguration file. - Functionalities for authenticated users are defined through the
functionalities:registeredvariable in thevars.yamlconfiguration file. - Functionalities for specific roles are defined in the database through the administration interface.
- Functionalities for specific users are defined in the database through the administration interface.
Each level overrides the previous ones in the order indicated above.
For example, if the user is authenticated and has associated functionalities in
the user database table, then the functionalities:anonymous and
functionalities:registered configuration variables, as well as any
functionality associated with his/her role, will be ignored.
Configuration¶
The vars.yaml file includes variables for managing functionalities.
admin_interface/available_functionalitiesList of functionality types that should be available in the administration interface (and added to the
functionalitytable in the database).For example:
admin_interface: available_functionalities: - default_basemap - print_template - mapserver_substitution
The following syntax is also accepted:
admin_interface: available_functionalities: [default_basemap, print_template, mapserver_substitution]
functionalities:anonymousFunctionalities accessible to anonymous users. This is a list of key/value pairs, whose values are either arrays or scalars.
For example:
functionalities: anonymous: print_template: - 1 A4 portrait - 2 A3 landscape default_basemap: plan
In this example, anonymous users can print maps using the
1 A4 portraitand2 A3 landscapelayouts only. Their default base layer is theplanlayer.functionalities:registered- Functionalities accessible to any authenticated user. This is a list of
key/value pairs, of the same form as for
functionalities:anonymous. functionalities:available_in_templatesFunctionalities that are made available to Mako templates (e.g.
viewer.js) through thefunctionalitytemplate variable.For example with:
functionalities: available_in_templates: - <functionality_name>
if a user is associated to, say,
<functionality_name>/value1and<functionality_name>/value2, then thefunctionalitytemplate variable will be set to a dict with one key/value pair:"<functionality_name>": ["value1","value2"].
Usage in JavaScript client¶
The functionalities will be sent to the JavaScript client application through the user web service.
To be published, a functionality should be present in the parameter functionalities:available_in_templates
parameter in the vars.yaml configuration file.
Using Functionalities to configure the basemap to use for each theme¶
A default basemap may be automatically loaded when the user selects a given
theme. This can be configured in the administration interface, as follows:
if not available yet, define a
default_basemap functionality containing the basemap reference. Edit the
theme and select the basemap to load in the default_basemap list. If
several default_basemap items are selected, only the first one will be
taken into account.
Extend the data model¶
The data model can be extended in the file geoportal/<package>_geoportal/models.py.
For example, to add some user details, including a link to a new class named “Title”,
add to geoportal/<package>_geoportal/models.py:
from deform.widget import HiddenWidget
from sqlalchemy import Column, types
from sqlalchemy import ForeignKey
from c2cgeoportal_commons.models.static import _schema, User
class UserDetail(User):
__tablename__ = 'userdetail'
__table_args__ = {'schema': _schema}
__mapper_args__ = {'polymorphic_identity': 'detailed'}
__colanderalchemy_config__ = {
'title': _('User detail'),
'plural': _('User details')
}
id = Column(
types.Integer,
ForeignKey(_schema + '.user.id'),
primary_key=True.
info={
"colanderalchemy": {
"missing": None,
"widget": HiddenWidget()
}
}
)
phone = Column(
types.Unicode,
info={
'colanderalchemy': {
'title': _('Phone')
}
}
)
# title
title_id = Column(Integer, ForeignKey(_schema + '.title.id'), nullable=False)
title = relationship("Title", backref=backref('users'), info={
'colanderalchemy': {
'title': _('Title')
}
})
class Title(Base):
__tablename__ = 'title'
__table_args__ = {'schema': _schema}
__colanderalchemy_config__ = {
'title': _('Title'),
'plural': _('Titles')
}
id = Column(types.Integer, primary_key=True)
name = Column(types.Unicode, nullable=False, info={
'colanderalchemy': {
'title': _('Name')
}
})
Now you need to extend the administration user interface. For this, first ensure that the following files exist (if needed, create them as empty files):
geoportal/<package>_geoportal/admin/__init__.py:geoportal/<package>_geoportal/admin/views/__init__.py:
Now, create a file geoportal/<package>_geoportal/admin/views/userdetail.py as follows:
from <package>_geoportal.models import UserDetail
from functools import partial
from pyramid.view import view_defaults
from pyramid.view import view_config
from sqlalchemy.orm import aliased, subqueryload
from deform.widget import FormWidget
from c2cgeoform.schema import GeoFormSchemaNode
from c2cgeoform.views.abstract_views import AbstractViews
from c2cgeoform.views.abstract_views import ListField
from c2cgeoportal_admin.schemas.roles import roles_schema_node
from cartoriviera_geoportal.models import UserDetail
_list_field = partial(ListField, UserDetail)
base_schema = GeoFormSchemaNode(UserDetail, widget=FormWidget(fields_template="user_fields"))
base_schema.add(roles_schema_node("roles"))
base_schema.add_unique_validator(UserDetail.username, UserDetail.id)
settings_role = aliased(Role)
@view_defaults(match_param='table=userdetail')
class UserDetailViews(AbstractViews):
_list_fields = [
_list_field('id'),
_list_field('username'),
_list_field('title'),
_list_field('email'),
_list_field('last_login'),
_list_field('expire_on'),
_list_field('deactivated'),
_list_field('phone'),
_list_field(
"settings_role",
renderer=lambda user: user.settings_role.name if user.settings_role else "",
sort_column=settings_role.name,
filter_column=settings_role.name,
),
_list_field(
"roles",
renderer=lambda user: ", ".join([r.name or "" for r in user.roles]),
filter_column=Role.name,
),
]
_id_field = 'id'
_model = UserDetail
_base_schema = base_schema
def _base_query(self):
return (
self._request.dbsession.query(UserDetail)
.distinct()
.outerjoin(settings_role, settings_role.id == UserDetail.settings_role_id)
.outerjoin("roles")
.options(subqueryload("settings_role"))
.options(subqueryload("roles"))
)
@view_config(
route_name='c2cgeoform_index',
renderer='./templates/index.jinja2'
)
def index(self):
return super().index()
@view_config(
route_name='c2cgeoform_grid',
renderer='fast_json'
)
def grid(self):
return super().grid()
@view_config(
route_name='c2cgeoform_item',
request_method='GET',
renderer='./templates/edit.jinja2'
)
def view(self):
return super().edit()
@view_config(
route_name='c2cgeoform_item',
request_method='POST',
renderer='./templates/edit.jinja2'
)
def save(self):
return super().save()
@view_config(
route_name='c2cgeoform_item',
request_method='DELETE',
renderer='fast_json'
)
def delete(self):
return super().delete()
@view_config(
route_name='c2cgeoform_item_duplicate',
request_method='GET',
renderer='./templates/edit.jinja2'
)
def duplicate(self):
return super().duplicate()
And now the file geoportal/<package>_geoportal/admin/views/title.py:
from <package>_geoportal.models import Title
from functools import partial
from pyramid.view import view_defaults
from pyramid.view import view_config
from c2cgeoform.schema import GeoFormSchemaNode
from c2cgeoform.views.abstract_views import AbstractViews
from c2cgeoform.views.abstract_views import ListField
base_schema = GeoFormSchemaNode(Title)
_list_field = partial(ListField, Title)
@view_defaults(match_param='table=title')
class TitleViews(AbstractViews):
_list_fields = [
_list_field('id'),
_list_field('name'),
]
_id_field = 'id'
_model = Title
_base_schema = base_schema
@view_config(
route_name='c2cgeoform_index',
renderer='./templates/index.jinja2'
)
def index(self):
return super().index()
@view_config(
route_name='c2cgeoform_grid',
renderer='fast_json'
)
def grid(self):
return super().grid()
@view_config(
route_name='c2cgeoform_item',
request_method='GET',
renderer='./templates/edit.jinja2'
)
def view(self):
return super().edit()
@view_config(
route_name='c2cgeoform_item',
request_method='POST',
renderer='./templates/edit.jinja2'
)
def save(self):
return super().save()
@view_config(
route_name='c2cgeoform_item',
request_method='DELETE',
renderer='fast_json'
)
def delete(self):
return super().delete()
@view_config(
route_name='c2cgeoform_item_duplicate',
request_method='GET',
renderer='./templates/edit.jinja2'
)
def duplicate(self):
return super().duplicate()
And finally in geoportal/<package>_geoportal/__init__.py replace config.scan() by:
# Add custom table in admin interface, that means re-add all normal table
from c2cgeoform.routes import register_models
from c2cgeoportal_commons.models.main import (
Role, LayerWMS, LayerWMTS, Theme, LayerGroup, Interface, OGCServer,
Functionality, RestrictionArea
)
from c2cgeoportal_commons.models.static import User
from c2cgeoportal_admin import PermissionSetter
from <package>_geoportal.models import UserDetail, Title
register_models(config, (
('themes', Theme),
('layer_groups', LayerGroup),
('layers_wms', LayerWMS),
('layers_wmts', LayerWMTS),
('ogc_servers', OGCServer),
('restriction_areas', RestrictionArea),
('userdetail', UserDetail),
('roles', Role),
('functionalities', Functionality),
('interfaces', Interface),
('title', Title),
), 'admin')
with PermissionSetter(config):
# Scan view decorator for adding routes
config.scan('<package>_geoportal.admin.views')
config.scan(ignore='<package>_geoportal.admin.views')
Build and run the application:
./build <args>
docker-compose up -d
Get and run the SQL command to create the tables:
Run Python console:
docker-compose exec geoportal python3
Execute the following code:
from c2c.template.config import config
config.init('/etc/config/config.yaml')
from sqlalchemy.schema import CreateTable
from <package>_geoportal.models import UserDetail, Title
print(CreateTable(UserDetail.__table__))
print(CreateTable(Title.__table__))
Run pSQL console:
docker-compose exec tools psql
And enter the SQL commands
Hide the no more used table, add it in your configuration:
vars:
...
admin_interface:
...
exclude_pages:
- users