Welcome to c2cgeoform’s documentation!¶
Prerequisites¶
The following system packages must be installed on your system:
python3-dev
python-virtualenv
libpq-dev
(header files for PostgreSQL)gettext
On Windows, you should install make
using Cygwin (and put the bin
folder into the path). For Python, please use Python >= 3.x.
You will also need NodeJS which can be installed by NVM : https://github.com/creationix/nvm#install-script
User guide¶
Creating a c2cgeoform project¶
This page describes how to create a c2cgeoform project. A c2cgeoform project is basically a Pyramid project with c2cgeoform enabled in the project.
Install c2cgeoform¶
git clone git@github.com:camptocamp/c2cgeoform.git
cd c2cgeoform
make build
On Windows, you should use the https way to clone the repository:
git clone https://github.com:camptocamp/c2cgeoform.git
Create a Pyramid project using c2cgeoform scaffold¶
Note that if PYTHONPATH does not exists as an environment variable, template files (*_tmpl) are not rendered in new project folder.
export PYTHONPATH=$PYTHONPATH
.build/venv/bin/pcreate -s c2cgeoform ../c2cgeoform_project
Initialize a git repository¶
Make your new project folder a git repository.
cd ../c2cgeoform_project
git init
git add .
git commit -m 'Initial commit'
Install the project and its dependencies¶
make build
Set up database¶
First of all you need to have a PostGIS database for the project. Create the database:
sudo -u postgres psql -c "CREATE USER \"www-data\" WITH PASSWORD 'www-data';"
sudo -u postgres createdb c2cgeoform_project
sudo -u postgres psql -d c2cgeoform_project -c 'CREATE EXTENSION postgis;'
sudo -u postgres psql -c 'GRANT ALL ON DATABASE c2cgeoform_project TO "www-data";'
When you do have a Postgres role and a PostGIS database edit the
development.ini
and production.ini
files and set
sqlachemy.url
appropriately. For example:
sqlalchemy.url = postgresql://www-data:www-data@localhost:5432/c2cgeoform_project
Now create the tables:
make initdb
Note that this will launch the python script
c2cgeoform_project/scripts/initializedb.py
. You will have to
customize this thereafter.
Run the development server¶
You are now ready to run the application:
make serve
Visit the following URLs to verify that the application works correctly: http://localhost:6543/excavations/new and http://localhost:6543/excavations.
Defining the model for a form¶
The underlying schema for a c2cgeoform
form is defined as a SQLAlchemy
model. A simple definition is shown below:
from sqlalchemy import (Column, Integer, Text)
import deform
from uuid import uuid4
from c2cgeoform.models import Base
class Comment(Base):
__tablename__ = 'comments'
__colanderalchemy_config__ = {
'title': 'A very simple form'
}
id = Column(Integer, primary_key=True, info={
'colanderalchemy': {
'widget': deform.widget.HiddenWidget()
}})
hash = Column(Text, unique=True, default=lambda: str(uuid4(), info={
'colanderalchemy': {
'widget': HiddenWidget()
}})
name = Column(Text, nullable=False, info={
'colanderalchemy': {
'title': 'Name'
}})
comment = Column(Text, nullable=True, info={
'colanderalchemy': {
'title': 'Comment',
'widget': deform.widget.TextAreaWidget(rows=3),
}})
This SQLAlchemy model is enriched with properties for ColanderAlchemy, for example to set a title for a field, use a specific Deform widget or use a Colander validator.
In general, every SQLAlchemy model can be used as schema for a form. The only requirements are:
- The model class must contain exactly one primary key column. Tables with composite primary keys are not supported.
A more complex example for a model can be found here. For more information on how to define the model, please refer to the SQLAlchemy, ColanderAlchemy, Colander and Deform documentations.
Create the views for your model¶
There is already a views class created in your project by the scaffold,
see file views/excavation.py
. Let’s have a look on that file content.
To ease creation of views classes, c2cgeoform comes with an abstract class that contains base methods to display grids, render forms and save data. This is why ExcavationViews extends AbstractViews for a specific SQLAlchemy model and colander schema:
@view_defaults(match_param='table=excavations')
class ExcavationViews(AbstractViews):
_model = Excavation
_base_schema = base_schema
Also note the @view_defaults
which says that all the views declared in this
class will only apply when the route parameter named table
will be equal to
"excavation"
. The routes given by c2cgeoform have the following form:
- c2cgeoform_index:
{table}
- c2cgeoform_grid:
{table}/grid.json
- c2cgeoform_item:
{table}/{{id}}
- c2cgeoform_item_duplicate:
{table}/{{id}}/duplicate
Those routes are registered in the pyramid config by the routes
module (see
the routes.py
file situated at the root of the generated project).
register_models(config, [
('excavations', Excavation)
To select records through urls, we also need a unique field, this is given by:
_id_field = 'hash'
And to show the table records grid we need a definition per column:
_list_fields = [
_list_field('reference_number'),
_list_field('request_date'),
...
]
Finally we need a method for each view, for a typical use case, we could have 6 views:
index
: Return HTML page with the grid.grid
: Return records as JSON for the grid.edit
: Show create or edit form for the specified record.duplicate
: Show duplication form for the specified record.delete
: Delete the specified record.save
: Save new record or modifications to existing record.
In a typical use case, those views will only call the super class method with the same name.
Configure the grid¶
Grid columns can be configured using the _list_fields
property of the views
class, which is an ordered list of ListField
objects, one for each column.
The ListField
constructor take some parameters:
model
: the SQLAlchemy mapper (required if attr is an attribute name).attr
: the model attribute name to use or an SQLAlchemy InstrumentedAttribute.key
: an identifier for the column, default toattribute.key
.label
: text for the column header, default to colanderalchemy title for the field.renderer
: callable that takes an entity of the SQLAlchemy mapper and returns a string value.sort_column
: AnIntrumentedAttribute
to use insort_by
.filter_column
: AnIntrumentedAttribute
to filter with.visible
: a boolean for the initial visible state of this column.
Every time the table index page asks for data from the grid view, the
AbstractView
will create a default query using AbstractViews._base_query
method.
If you use columns coming from relationships, this might result in sending one
request to the database for each relationship and each record.
In such cases, you should override the _base_query
method to use eager
loading for those relationships, for example:
def _base_query(self):
return self._request.dbsession.query(Excavation).distinct(). \
join('situations'). \
options(subqueryload('situations'))
Note that you also need to join
the relationships you use for sorting and filtering.
Understanding the schemas¶
ColanderAlchemy allows creating Colander schemas directly from SQLAlchemy model classes.
Additionally, c2cgeoform provides its own classes with extended features. A basic use case schema creation will look like:
from model import MyClass
schema = GeoFormSchemaNode(MyClass)
See the following API to understand what is going on behind the scene.
-
class
c2cgeoform.schema.
GeoFormSchemaNode
(*args, **kw)¶ An SQLAlchemySchemaNode with deferred request and dbsession properties. This will allow defining schemas that requires the request and dbsession at module-scope.
Example usage:
schema = GeoFormSchemaNode(MyModel) def create_form(request, dbsession): return Form( schema = schema.bind( request=request, dbsession=request.dbsession), ... )
-
add_unique_validator
(column, column_id)¶ Adds an unique validator on this schema instance.
- column
- SQLAlchemy ColumnProperty that should be unique.
- column_id
- SQLAlchemy MapperProperty that is used to recognize the entity, basically the primary key ColumnProperty.
-
-
class
c2cgeoform.schema.
GeoFormManyToManySchemaNode
(class_, includes=None, *args, **kw)¶ A GeoFormSchemaNode that properly handles many to many relationships.
- includes:
- Default to primary key name(s) only.
-
objectify
(dict_, context=None)¶ Method override that returns the existing ORM class instance instead of creating a new one.
-
c2cgeoform.schema.
manytomany_validator
(node, cstruct)¶ Validator function that checks if
cstruct
values exist in the related table.Note that entities are retrieved using only one query and placed in SQLAlchemy identity map before looping on
cstruct
.
-
class
c2cgeoform.ext.colander_ext.
BinaryData
¶ A Colander type meant to be used with
LargeBinary
columns.Example usage
class Model(): id = Column(Integer, primary_key=True) data = Colum(LargeBinary, info={ 'colanderalchemy': { 'typ': colander_ext.BinaryData() }})
It is usually not used directly in application models, but through the
c2cgeoform.models.FileData
mixin, which is meant to be used with adeform_ext.FileUploadWidget
.The serialize method just returns
colander.null
. This is because theFileUploadWidget
’s template does not use and need the binary data.The deserialize method gets a Python
file
object and returns a bytes string that is appropriate for the database.-
deserialize
(node, cstruct)¶ In Colander speak: Converts a serialized value (a cstruct) into a Python data structure (a appstruct). Or: Converts a Python file stream to plain binary data.
-
serialize
(node, appstruct)¶ In Colander speak: Converts a Python data structure (an appstruct) into a serialization (a cstruct).
-
-
class
c2cgeoform.ext.colander_ext.
Geometry
(geometry_type='GEOMETRY', srid=-1, map_srid=-1)¶ A Colander type meant to be used with GeoAlchemy 2 geometry columns.
Example usage
geom = Column( geoalchemy2.Geometry('POLYGON', 4326, management=True), info={ 'colanderalchemy': { 'typ': colander_ext.Geometry( 'POLYGON', srid=4326, map_srid=3857), 'widget': deform_ext.MapWidget() }})
Attributes/Arguments
- geometry_type
- The geometry type should match the column geometry type.
- srid
- The SRID of the geometry should also match the column definition.
- map_srid
- The projection used for the OpenLayers map. The geometries will be reprojected to this projection.
-
deserialize
(node, cstruct)¶ In Colander speak: Converts a serialized value (a cstruct) into a Python data structure (a appstruct). Or: Converts a GeoJSON string into a WKBElement.
-
serialize
(node, appstruct)¶ In Colander speak: Converts a Python data structure (an appstruct) into a serialization (a cstruct). Or: Converts a WKBElement into a GeoJSON string.
Configure the widgets¶
All Deform widgets can be used with c2cgeoform
. See the Deform examples
and widgets API reference for detailed description about available options.
Additionally, c2cgeoform provides some extra widgets:
Using custom templates¶
c2cgeoform distinguishes two types of templates: views templates and widget templates. - Views templates are used directly by Pyramid and provide the site structure. - Widgets templates are used by Deform to render the forms.
Default views templates¶
The default c2cgeoform views templates are located in the templates
folder and use jinja2 syntax.
c2cgeoform comes with partial templates that are included in views templates of your project.
Overriding widgets templates globally¶
Deform widget templates are located in the templates/widgets
folder and
use the chameleon syntax.
At rendering time, Deform will search folders for the templates in order they
appear in Form renderer search_path
property. c2cgeoform configure it to:
default_search_paths = (
resource_filename('c2cgeoform', 'templates/widgets'),
resource_filename('deform', 'templates'))
But you can add you own widgets folder, in your package __init__.py
file
before including c2cgeoform
using:
import c2cgeoform
search_paths = (
(resource_filename(__name__, 'templates/widgets'),) +
c2cgeoform.default_search_paths
)
c2cgeoform.default_search_paths = search_paths
To overwrite globally the Deform templates or the templates coming from
c2cgeoform
(like the map widget), you just need to copy the template to your application
templates/widgets
folder.
Use a custom template for a form or a specific widget in a form¶
Both the form main template and widget templates can be changed locally for a
given model by giving a template
property to the Widget
.
base_schema = GeoFormSchemaNode(
Comment,
widget=FormWidget(template='comment'))
Note that it is possible to create a layout for the form fields without completely
overriding the form template by giving a fields_template
to the form schema.
base_schema = GeoFormSchemaNode(
Comment,
widget=FormWidget(fields_template='comment_fields'))
Here is the default one: https://github.com/camptocamp/c2cgeoform/blob/master/c2cgeoform/templates/widgets/mapping_fields.pt
Writing tests¶
Internationalization¶
Developer guide¶
This page describes how to set up the development environment for working on c2cgeoform. It is for developers working on c2cgeoform itself, not for developers working on c2cgeoform-based applications.
Note that c2cgeoform is a framework with a Pyramid scaffold used to create c2cgeoform-based applications. This scaffold produce a fully functional c2cgeoform-base project: the c2cgeoform_demo project.
When running code checks and tests, these jobs are first run on the c2cgeoform framework itself. Then the c2cgeoform_demo project is generated in .build folder. Finally, the checks and tests are launched in this project.
Note that you should never alter the c2cgeoform_demo project itself but the c2cgeoform scaffold and regenerate the c2cgeoform_demo project.
Clone the project¶
git clone git@github.com:camptocamp/c2cgeoform.git
cd c2cgeoform
Run the checks¶
make check
Run the tests¶
Create the tests database:
sudo -u postgres psql -c "CREATE USER \"www-data\" WITH PASSWORD 'www-data';"
export DATABASE=c2cgeoform_demo_tests
sudo -u postgres psql -d postgres -c "CREATE DATABASE $DATABASE OWNER \"www-data\";"
sudo -u postgres psql -d $DATABASE -c "CREATE EXTENSION postgis;"
Run the framework and demo tests:
make test
Serve the c2cgeoform_demo project¶
You need to create a PostGIS database. For example:
export DATABASE=c2cgeoform_demo
sudo -u postgres psql -d postgres -c "CREATE DATABASE $DATABASE OWNER \"www-data\";"
sudo -u postgres psql -d $DATABASE -c "CREATE EXTENSION postgis;"
make initdb
Run the development server:
make serve
You can now open the demo project in your favorite browser: http://localhost:6543/
And there you go, you’re ready to develop, make changes in c2cgeoform, run checks and tests in c2cgeoform. And finally see the results in c2cgeoform demo application.
Deploy the c2cgeoform_demo on demo server¶
Prepare the demo project:
# open a ssh connection with the GMF 2.3 server
ssh -A geomapfish-demo.camptocamp.com
# clone the c2cgeoform repository
cd /var/www/vhosts/geomapfish-demo/private
git clone git@github.com:camptocamp/c2cgeoform.git
# generate the c2cgeoform_demo project with mod_wsgi related files
APACHE_ENTRY_POINT=c2cgeoform make modwsgi
Create the database as to serve the development version, see: Serve the c2cgeoform_demo project
Include the demo project in Apache virtual host configuration:
echo "IncludeOptional $PWD/.build/c2cgeoform_demo/.build/apache.conf" > /var/www/vhosts/geomapfish-demo/conf/c2cgeoform_demo.conf
sudo apache2ctl configtest
If everything goes fine, restart apache:
sudo apache2ctl graceful
You can now open the demo project in your favorite browser: https://geomapfish-demo.camptocamp.com/c2cgeoform/