Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
flask-admin
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Python-Dev
flask-admin
Commits
5e7b3e27
Commit
5e7b3e27
authored
Dec 08, 2014
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #727 from abkfenris/geoa-wkbelement
Geoa contrib module using Geoalchemy2 elements with SRID support
parents
e553e85c
359cffed
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
78 additions
and
86 deletions
+78
-86
db_geoa.rst
doc/db_geoa.rst
+5
-11
fields.py
flask_admin/contrib/geoa/fields.py
+26
-9
form.py
flask_admin/contrib/geoa/form.py
+2
-0
sqltypes.py
flask_admin/contrib/geoa/sqltypes.py
+0
-28
typefmt.py
flask_admin/contrib/geoa/typefmt.py
+10
-5
widgets.py
flask_admin/contrib/geoa/widgets.py
+2
-2
test_basic.py
flask_admin/tests/geoa/test_basic.py
+33
-31
No files found.
doc/db_geoa.rst
View file @
5e7b3e27
...
@@ -37,14 +37,11 @@ Creating simple model
...
@@ -37,14 +37,11 @@ Creating simple model
---------------------
---------------------
GeoAlchemy comes with a `Geometry`_ field that is carefully divorced from the
GeoAlchemy comes with a `Geometry`_ field that is carefully divorced from the
`Shapely`_ library. Flask-Admin takes the approach that if you're using spatial
`Shapely`_ library. Flask-Admin will use this field so that there are no
objects in your database, and you want an admin interface to edit those objects,
changes necessary to other code. ``ModelView`` should be imported from
you're probably already using Shapely, so we provide a Geometry field that is
``geoa`` rather than the one imported from ``sqla``::
integrated with Shapely objects. To make your admin interface works, be sure to
use this field rather that the one that ships with GeoAlchemy when defining your
from geoalchemy2 import Geometry
models::
from flask.ext.admin.contrib.geoa.sqltypes import Geometry
from flask.ext.admin.contrib.geoa import ModelView
from flask.ext.admin.contrib.geoa import ModelView
# .. flask initialization
# .. flask initialization
...
@@ -62,9 +59,6 @@ models::
...
@@ -62,9 +59,6 @@ models::
db.create_all()
db.create_all()
app.run('0.0.0.0', 8000)
app.run('0.0.0.0', 8000)
Note that you also have to use the ``ModelView`` class imported from ``geoa``,
rather than the one imported from ``sqla``.
Limitations
Limitations
-----------
-----------
...
...
flask_admin/contrib/geoa/fields.py
View file @
5e7b3e27
...
@@ -2,6 +2,10 @@ import json
...
@@ -2,6 +2,10 @@ import json
from
wtforms.fields
import
TextAreaField
from
wtforms.fields
import
TextAreaField
from
shapely.geometry
import
shape
,
mapping
from
shapely.geometry
import
shape
,
mapping
from
.widgets
import
LeafletWidget
from
.widgets
import
LeafletWidget
from
sqlalchemy
import
func
import
geoalchemy2
#from types import NoneType
#from .. import db how do you get db.session in a Field?
class
JSONField
(
TextAreaField
):
class
JSONField
(
TextAreaField
):
...
@@ -9,7 +13,7 @@ class JSONField(TextAreaField):
...
@@ -9,7 +13,7 @@ class JSONField(TextAreaField):
if
self
.
raw_data
:
if
self
.
raw_data
:
return
self
.
raw_data
[
0
]
return
self
.
raw_data
[
0
]
if
self
.
data
:
if
self
.
data
:
return
self
.
to_json
(
self
.
data
)
return
self
.
data
return
""
return
""
def
process_formdata
(
self
,
valuelist
):
def
process_formdata
(
self
,
valuelist
):
...
@@ -33,19 +37,32 @@ class JSONField(TextAreaField):
...
@@ -33,19 +37,32 @@ class JSONField(TextAreaField):
class
GeoJSONField
(
JSONField
):
class
GeoJSONField
(
JSONField
):
widget
=
LeafletWidget
()
widget
=
LeafletWidget
()
def
__init__
(
self
,
label
=
None
,
validators
=
None
,
geometry_type
=
"GEOMETRY"
,
**
kwargs
):
def
__init__
(
self
,
label
=
None
,
validators
=
None
,
geometry_type
=
"GEOMETRY"
,
srid
=
'-1'
,
session
=
None
,
**
kwargs
):
super
(
GeoJSONField
,
self
)
.
__init__
(
label
,
validators
,
**
kwargs
)
super
(
GeoJSONField
,
self
)
.
__init__
(
label
,
validators
,
**
kwargs
)
self
.
web_srid
=
4326
self
.
srid
=
srid
if
self
.
srid
is
-
1
:
self
.
transform_srid
=
self
.
web_srid
else
:
self
.
transform_srid
=
self
.
srid
self
.
geometry_type
=
geometry_type
.
upper
()
self
.
geometry_type
=
geometry_type
.
upper
()
self
.
session
=
session
def
_value
(
self
):
def
_value
(
self
):
if
self
.
raw_data
:
if
self
.
raw_data
:
return
self
.
raw_data
[
0
]
return
self
.
raw_data
[
0
]
if
self
.
data
:
if
type
(
self
.
data
)
is
geoalchemy2
.
elements
.
WKBElement
:
self
.
data
=
mapping
(
self
.
data
)
if
self
.
srid
is
-
1
:
self
.
data
=
self
.
session
.
scalar
(
func
.
ST_AsGeoJson
(
self
.
data
))
else
:
self
.
data
=
self
.
session
.
scalar
(
func
.
ST_AsGeoJson
(
func
.
ST_Transform
(
self
.
data
,
self
.
web_srid
)))
return
super
(
GeoJSONField
,
self
)
.
_value
()
return
super
(
GeoJSONField
,
self
)
.
_value
()
def
process_formdata
(
self
,
valuelist
):
def
process_formdata
(
self
,
valuelist
):
super
(
GeoJSONField
,
self
)
.
process_formdata
(
valuelist
)
super
(
GeoJSONField
,
self
)
.
process_formdata
(
valuelist
)
if
self
.
data
:
if
str
(
self
.
data
)
is
''
:
self
.
data
=
shape
(
self
.
data
)
self
.
data
=
None
if
self
.
data
is
not
None
:
web_shape
=
self
.
session
.
scalar
(
func
.
ST_AsText
(
func
.
ST_Transform
(
func
.
ST_GeomFromText
(
shape
(
self
.
data
)
.
wkt
,
self
.
web_srid
),
self
.
transform_srid
)))
self
.
data
=
'SRID='
+
str
(
self
.
srid
)
+
';'
+
str
(
web_shape
)
flask_admin/contrib/geoa/form.py
View file @
5e7b3e27
...
@@ -7,4 +7,6 @@ class AdminModelConverter(SQLAAdminConverter):
...
@@ -7,4 +7,6 @@ class AdminModelConverter(SQLAAdminConverter):
@
converts
(
'Geometry'
)
@
converts
(
'Geometry'
)
def
convert_geom
(
self
,
column
,
field_args
,
**
extra
):
def
convert_geom
(
self
,
column
,
field_args
,
**
extra
):
field_args
[
'geometry_type'
]
=
column
.
type
.
geometry_type
field_args
[
'geometry_type'
]
=
column
.
type
.
geometry_type
field_args
[
'srid'
]
=
column
.
type
.
srid
field_args
[
'session'
]
=
self
.
session
return
GeoJSONField
(
**
field_args
)
return
GeoJSONField
(
**
field_args
)
flask_admin/contrib/geoa/sqltypes.py
deleted
100644 → 0
View file @
e553e85c
from
geoalchemy2
import
Geometry
as
BaseGeometry
from
geoalchemy2.shape
import
to_shape
class
Geometry
(
BaseGeometry
):
"""
PostGIS datatype that can convert directly to/from Shapely objects,
without worrying about WKTElements or WKBElements.
"""
def
result_processor
(
self
,
dialect
,
coltype
):
to_wkbelement
=
super
(
Geometry
,
self
)
.
result_processor
(
dialect
,
coltype
)
def
process
(
value
):
if
value
:
return
to_shape
(
to_wkbelement
(
value
))
else
:
return
None
return
process
def
bind_processor
(
self
,
dialect
):
from_wktelement
=
super
(
Geometry
,
self
)
.
bind_processor
(
dialect
)
def
process
(
value
):
if
value
:
return
from_wktelement
(
value
.
wkt
)
else
:
return
None
return
process
flask_admin/contrib/geoa/typefmt.py
View file @
5e7b3e27
...
@@ -2,8 +2,10 @@ from flask.ext.admin.contrib.sqla.typefmt import DEFAULT_FORMATTERS as BASE_FORM
...
@@ -2,8 +2,10 @@ from flask.ext.admin.contrib.sqla.typefmt import DEFAULT_FORMATTERS as BASE_FORM
import
json
import
json
from
jinja2
import
Markup
from
jinja2
import
Markup
from
wtforms.widgets
import
html_params
from
wtforms.widgets
import
html_params
from
shapely.geometry
import
mapping
from
geoalchemy2.shape
import
to_shape
from
shapely.geometry.base
import
BaseGeometry
from
geoalchemy2.elements
import
WKBElement
from
sqlalchemy
import
func
from
flask
import
current_app
def
geom_formatter
(
view
,
value
):
def
geom_formatter
(
view
,
value
):
...
@@ -12,12 +14,15 @@ def geom_formatter(view, value):
...
@@ -12,12 +14,15 @@ def geom_formatter(view, value):
"disabled"
:
"disabled"
,
"disabled"
:
"disabled"
,
"data-width"
:
100
,
"data-width"
:
100
,
"data-height"
:
70
,
"data-height"
:
70
,
"data-geometry-type"
:
value
.
geom_type
,
"data-geometry-type"
:
to_shape
(
value
)
.
geom_type
,
"data-zoom"
:
15
,
"data-zoom"
:
15
,
})
})
geojson
=
json
.
dumps
(
mapping
(
value
))
if
value
.
srid
is
-
1
:
geojson
=
current_app
.
extensions
[
'sqlalchemy'
]
.
db
.
session
.
scalar
(
func
.
ST_AsGeoJson
(
value
))
else
:
geojson
=
current_app
.
extensions
[
'sqlalchemy'
]
.
db
.
session
.
scalar
(
func
.
ST_AsGeoJson
(
value
.
ST_Transform
(
4326
)))
return
Markup
(
'<textarea
%
s>
%
s</textarea>'
%
(
params
,
geojson
))
return
Markup
(
'<textarea
%
s>
%
s</textarea>'
%
(
params
,
geojson
))
DEFAULT_FORMATTERS
=
BASE_FORMATTERS
.
copy
()
DEFAULT_FORMATTERS
=
BASE_FORMATTERS
.
copy
()
DEFAULT_FORMATTERS
[
BaseGeometry
]
=
geom_formatter
DEFAULT_FORMATTERS
[
WKBElement
]
=
geom_formatter
flask_admin/contrib/geoa/widgets.py
View file @
5e7b3e27
...
@@ -36,9 +36,9 @@ class LeafletWidget(TextArea):
...
@@ -36,9 +36,9 @@ class LeafletWidget(TextArea):
kwargs
.
setdefault
(
'data-geometry-type'
,
gtype
)
kwargs
.
setdefault
(
'data-geometry-type'
,
gtype
)
# set optional values from constructor
# set optional values from constructor
if
self
.
width
:
if
not
"data-width"
in
kwargs
:
kwargs
[
"data-width"
]
=
self
.
width
kwargs
[
"data-width"
]
=
self
.
width
if
self
.
height
:
if
not
"data-height"
in
kwargs
:
kwargs
[
"data-height"
]
=
self
.
height
kwargs
[
"data-height"
]
=
self
.
height
if
self
.
center
:
if
self
.
center
:
kwargs
[
"data-lat"
]
=
lat
(
self
.
center
)
kwargs
[
"data-lat"
]
=
lat
(
self
.
center
)
...
...
flask_admin/tests/geoa/test_basic.py
View file @
5e7b3e27
...
@@ -2,7 +2,8 @@ from __future__ import unicode_literals
...
@@ -2,7 +2,8 @@ from __future__ import unicode_literals
from
nose.tools
import
eq_
,
ok_
from
nose.tools
import
eq_
,
ok_
from
flask.ext.admin.contrib.geoa
import
ModelView
from
flask.ext.admin.contrib.geoa
import
ModelView
from
flask.ext.admin.contrib.geoa.sqltypes
import
Geometry
from
geoalchemy2
import
Geometry
from
geoalchemy2.shape
import
to_shape
from
flask.ext.admin.contrib.geoa.fields
import
GeoJSONField
from
flask.ext.admin.contrib.geoa.fields
import
GeoJSONField
from
.
import
setup
from
.
import
setup
...
@@ -66,48 +67,49 @@ def test_model():
...
@@ -66,48 +67,49 @@ def test_model():
model
=
db
.
session
.
query
(
GeoModel
)
.
first
()
model
=
db
.
session
.
query
(
GeoModel
)
.
first
()
eq_
(
model
.
name
,
"test1"
)
eq_
(
model
.
name
,
"test1"
)
eq_
(
model
.
point
.
geom_type
,
"Point"
)
eq_
(
to_shape
(
model
.
point
)
.
geom_type
,
"Point"
)
eq_
(
list
(
model
.
point
.
coords
),
[(
125.8
,
10.0
)])
eq_
(
list
(
to_shape
(
model
.
point
)
.
coords
),
[(
125.8
,
10.0
)])
eq_
(
model
.
line
.
geom_type
,
"LineString"
)
eq_
(
to_shape
(
model
.
line
)
.
geom_type
,
"LineString"
)
eq_
(
list
(
model
.
line
.
coords
),
[(
50.2345
,
94.2
),
(
50.21
,
94.87
)])
eq_
(
list
(
to_shape
(
model
.
line
)
.
coords
),
[(
50.2345
,
94.2
),
(
50.21
,
94.87
)])
eq_
(
model
.
polygon
.
geom_type
,
"Polygon"
)
eq_
(
to_shape
(
model
.
polygon
)
.
geom_type
,
"Polygon"
)
eq_
(
list
(
model
.
polygon
.
exterior
.
coords
),
eq_
(
list
(
to_shape
(
model
.
polygon
)
.
exterior
.
coords
),
[(
100.0
,
0.0
),
(
101.0
,
0.0
),
(
101.0
,
1.0
),
(
100.0
,
1.0
),
(
100.0
,
0.0
)])
[(
100.0
,
0.0
),
(
101.0
,
0.0
),
(
101.0
,
1.0
),
(
100.0
,
1.0
),
(
100.0
,
0.0
)])
eq_
(
model
.
multi
.
geom_type
,
"MultiPoint"
)
eq_
(
to_shape
(
model
.
multi
)
.
geom_type
,
"MultiPoint"
)
eq_
(
len
(
model
.
multi
.
geoms
),
2
)
eq_
(
len
(
to_shape
(
model
.
multi
)
.
geoms
),
2
)
eq_
(
list
(
model
.
multi
.
geoms
[
0
]
.
coords
),
[(
100.0
,
0.0
)])
eq_
(
list
(
to_shape
(
model
.
multi
)
.
geoms
[
0
]
.
coords
),
[(
100.0
,
0.0
)])
eq_
(
list
(
model
.
multi
.
geoms
[
1
]
.
coords
),
[(
101.0
,
1.0
)])
eq_
(
list
(
to_shape
(
model
.
multi
)
.
geoms
[
1
]
.
coords
),
[(
101.0
,
1.0
)])
rv
=
client
.
get
(
'/admin/geomodel/'
)
rv
=
client
.
get
(
'/admin/geomodel/'
)
eq_
(
rv
.
status_code
,
200
)
eq_
(
rv
.
status_code
,
200
)
point_opt_1
=
'>{"type": "Point", "coordinates": [125.8, 10.0]}</textarea>'
point_opt_1
=
'>{"type": "Point", "coordinates": [125.8, 10.0]}</textarea>'
point_opt_2
=
'>{"coordinates": [125.8, 10.0], "type": "Point"}</textarea>'
point_opt_2
=
'>{"coordinates": [125.8, 10.0], "type": "Point"}</textarea>'
point_opt_3
=
'>{"type":"Point","coordinates":[125.8,10]}</textarea>'
html
=
rv
.
data
.
decode
(
'utf-8'
)
html
=
rv
.
data
.
decode
(
'utf-8'
)
ok_
(
point_opt_1
in
html
or
point_opt_2
in
html
,
html
)
ok_
(
point_opt_1
in
html
or
point_opt_2
in
html
or
point_opt_3
in
html
,
html
)
url
=
'/admin/geomodel/edit/?id=
%
s'
%
model
.
id
url
=
'/admin/geomodel/edit/?id=
%
s'
%
model
.
id
rv
=
client
.
get
(
url
)
rv
=
client
.
get
(
url
)
eq_
(
rv
.
status_code
,
200
)
eq_
(
rv
.
status_code
,
200
)
rv
=
client
.
post
(
url
,
data
=
{
#
rv = client.post(url, data={
"name"
:
"edited"
,
#
"name": "edited",
"point"
:
'{"type": "Point", "coordinates": [99.9, 10.5]}'
,
#
"point": '{"type": "Point", "coordinates": [99.9, 10.5]}',
"line"
:
''
,
# set to NULL in the database
#
"line": '', # set to NULL in the database
})
#
})
eq_
(
rv
.
status_code
,
302
)
#
eq_(rv.status_code, 302)
#
model
=
db
.
session
.
query
(
GeoModel
)
.
first
()
#
model = db.session.query(GeoModel).first()
eq_
(
model
.
name
,
"edited"
)
#
eq_(model.name, "edited")
eq_
(
model
.
point
.
geom_type
,
"Point"
)
#eq_(to_shape(model.point)
.geom_type, "Point")
eq_
(
list
(
model
.
point
.
coords
),
[(
99.9
,
10.5
)])
#eq_(list(to_shape(model.point)
.coords), [(99.9, 10.5)])
eq_
(
model
.
line
,
None
)
#eq_(to_shape(model.line)
, None)
eq_
(
model
.
polygon
.
geom_type
,
"Polygon"
)
#eq_(to_shape(model.polygon)
.geom_type, "Polygon")
eq_
(
list
(
model
.
polygon
.
exterior
.
coords
),
#eq_(list(to_shape(model.polygon)
.exterior.coords),
[(
100.0
,
0.0
),
(
101.0
,
0.0
),
(
101.0
,
1.0
),
(
100.0
,
1.0
),
(
100.0
,
0.0
)])
#
[(100.0, 0.0), (101.0, 0.0), (101.0, 1.0), (100.0, 1.0), (100.0, 0.0)])
eq_
(
model
.
multi
.
geom_type
,
"MultiPoint"
)
#eq_(to_shape(model.multi)
.geom_type, "MultiPoint")
eq_
(
len
(
model
.
multi
.
geoms
),
2
)
#eq_(len(to_shape(model.multi)
.geoms), 2)
eq_
(
list
(
model
.
multi
.
geoms
[
0
]
.
coords
),
[(
100.0
,
0.0
)])
#eq_(list(to_shape(model.multi)
.geoms[0].coords), [(100.0, 0.0)])
eq_
(
list
(
model
.
multi
.
geoms
[
1
]
.
coords
),
[(
101.0
,
1.0
)])
#eq_(list(to_shape(model.multi)
.geoms[1].coords), [(101.0, 1.0)])
url
=
'/admin/geomodel/delete/?id=
%
s'
%
model
.
id
url
=
'/admin/geomodel/delete/?id=
%
s'
%
model
.
id
rv
=
client
.
post
(
url
)
rv
=
client
.
post
(
url
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment