django-floppyforms provides fields and rich widgets for easy manipulation of GEOS geometry fields. All geometry types are supported thanks to OpenLayers and a custom WKT parser/serializer implementing some Django-specific tweaks.
Note
Since GeoDjango doesn’t provide any rich widget out of the box (except for the admin), the API described here is not trying to look like any existing API in GeoDjango.
The geographic fields and widgets are provided under the floppyforms.gis namespace.
To make sure you’re ready to use the geographic widgets, follow the installation instructions for GeoDjango closely. You need to have 'django.contrib.gis' in your INSTALLED_APPS setting.
Next, you need to serve the javascript library provided by django-floppyforms (located in floppyforms/static/floppyforms/js/MapWidget.js).
You might want to use django.contrib.staticfiles, so that the javascript library will be picked up automatically and gets served by the development server. Just make sure you run manage.py collectstatic once you deploy your project.
django-floppyforms provides base widgets and geometry-specific widgets:
To get a fully working geometry widget, you need to define a class that inherits from a base widget (to specify the map provider) and a geometry-specific widget (to specify the type of geometry you want to create). Here is a quick example:
import floppyforms as forms
class PointWidget(forms.gis.PointWidget, forms.gis.BaseGMapWidget):
pass
Here BaseGMapWidget is the base widget (i.e. I want to see a Google Map) and PointWidget is the geometry-specific widget (i.e. I want to draw a point on the map).
The following base widgets are provided:
For each geographic model field, here are the corresponding form fields and form widgets provided by django-floppyforms:
GeoDjango model field | Floppyforms form field | Floppyforms form widget |
---|---|---|
PointField | PointField | PointWidget |
MultiPointField | MultiPointField | MultiPointWidget |
LineStringField | LineStringField | LineStringWidget |
MultiLineStringField | MultiLineStringField | MultiLineStringWidget |
PolygonField | PolygonField | PolygonWidget |
MultiPolygonField | MultiPolygonField | MultiPolygonWidget |
GeometryField | GeometryField | GeometryWidget |
GeometryCollectionField | GeometryCollectionField | GeometryCollectionWidget |
Each form field has a default form widget, using the corresponding geometry-specific widget and the Metacarta base widget. A form defined using nothing more than floppyforms fields will be displayed using the Metacarta WMS map service. For instance:
# forms.py
import floppyforms as forms
class GeoForm(forms.Form):
point = forms.gis.PointField()
{# template.html #}
<html>
<head>
{{ form.media }}
</head>
<body>
<form method="post" action="/some-url/">
{% csrf_token %}
{{ form.as_p }}
<p><input type="submit" value="Submit"></p>
</form>
</body>
</html>
And the result will looks like this:
The philosophy of this widgets library is to avoid building a complex layer of abstraction that would generate some javascript / OpenLayers code out of Python class attributes or methods. Everything that can be done in the template or JavaScript code should be done there.
Therefore there are few options to customize the map on the widget classes. Only basic customization can be made in python, the rest should be done in the templates using the JavaScript library.
The following attributes can be set on the widget class:
These options can be set as class attributes or passed into the attrs dictionnary used when instantiating a widget. The following snippets are equivalent:
import floppyforms as forms
class GMapPointWidget(forms.gis.PointWidget, forms.gis.BaseGMapWidget):
pass
class CustomPointWidget(GMapPointWidget):
map_width = 1000
map_height = 700
class GeoForm(forms.Form):
point = forms.gis.PointField(widget=CustomPointWidget)
and:
import floppyforms as forms
class GMapPointWidget(forms.gis.PointWidget, forms.gis.BaseGMapWidget):
pass
class GeoForm(forms.Form):
point = forms.gis.PointField(widget=GMapPointWidget(attrs={
'map_width': 1000,
'map_height': 700,
}))
Of course, the traditional template_name class attribute is also supported.
The following variables are available in the template context:
The javascript library provided by django-floppyforms relies on OpenLayers. It creates a map container based on a series of options. A minimal widget can be created like this:
var options = {
geom_type: OpenLayers.Geometry.Point,
id: 'id_point',
is_point: true,
map_id: 'point_map',
name: 'My awesome point'
};
var point_map = new MapWidget(options);
With these options, you need in your HTML code a <textarea id="id_point"> and an empty <div id="point_map">. The size of the map can be set by styling the div with CSS.
Generally you don’t have to touch the geom_type, id, is_point, map_id and name attributes: django-floppyforms generates them for you. However, the template structure makes it easy to specify some custom options. The base template defines a map_options and an options block. They can be altered like this (let’s say we want to re-implement the Google Maps base widget):
# forms.py
import floppyforms as forms
class BaseGMapWidget(forms.gis.BaseGeometryWidget):
map_srid = 900913 # Use the google projection
template_name = 'forms/google_map.html'
class Media:
js = (
'http://openlayers.org/dev/OpenLayers.js',
'floppyforms/js/MapWidget.js',
'http://maps.google.com/maps/api/js?sensor=false',
)
Here we need the development version of OpenLayers because OpenLayers 2.10 doesn’t implement version 3 of the Google Maps API. We also specify that we’re using the google projection.
{# forms/google_map.html #}
{% extends "floppyforms/gis/openlayers.html" %}
{% block options %}
{{ block.super }}
options['base_layer'] = new OpenLayers.Layer.Google("Google Streets",
{numZoomLevels: 20,
units: 'm'});
options['point_zoom'] = 14;
{% endblock %}
Calling block.super generates the options dictionnary with all the required options. We can then safely alter it at will. In this case we can directly add an OpenLayers.Layer instance to the map options and it will be picked up as a base layer.
The following options can be passed to the widget constructor:
There is also a map_options block that can be overridden. Its purpose is to declare a map_options dictionnary that can be passed to the OpenLayers.Map constructor. For instance:
{% block map_options %}
var map_options = {
maxExtend: new OpenLayers.Bounds(-20037508,-20037508,20037508,20037508),
maxResolution: 156543.0339,
numZoomLevels: 20,
units: 'm'
};
{% endblock %}
Here we don’t need to call block.super since the base template only instantiates an empty dictionnary.
If the options or the map options don’t give you enough flexibility, you can, not necessarily in that order:
In either way, digging into floppyforms’ code (templates, widgets, javascript lib) is more than encouraged. Of course, if you end up implementing additional base widgets for new map providers, feel free to contribute them back!
If you need a custom base widget, it is important to inherit from floppyforms.gis.BaseGeometryWidget: if you inherit from an existing base widget, you may end up with conflicting media files. BaseGeometryWidget doesn’t specify any javascript file so get more control by subclassing it.
# forms.py
import floppyforms as forms
class OsmLineStringWidget(forms.gis.BaseOsmWidget,
forms.gis.LineStringWidget):
pass
class OsmForm(forms.Form):
line = forms.gis.LineStringField(widget=OsmLineStringWidget)
Result:
# forms.py
import floppyforms as forms
class GMapPolygonWidget(forms.gis.BaseGMapWidget,
forms.gis.PolygonWidget):
pass
class GmapForm(forms.Form):
poly = forms.gis.PolygonField(widget=GMapPolygonWidget)
Result: