Codebase list djangorestframework-gis / 2e7c34d
Import djangorestframework-gis_0.9.5.orig.tar.gz Brian May 8 years ago
38 changed file(s) with 1236 addition(s) and 303 deletion(s). Raw diff Collapse all Expand all
0 [run]
1 omit =
2 /*/__init__.py
77 .coverage
88 *~
99 ._*
10 *.DS_Store
10 *.DS_Store
11 *.komodoproject
12 /htmlcov
0 sudo: false
01 language: python
2 cache: pip
13
24 services:
35 - postgresql
57 python:
68 - "3.4"
79 - "3.3"
10 - "3.2"
811 - "2.7"
912 - "2.6"
1013
1114 env:
12 - DJANGO="django==1.7.4"
13 - DJANGO="django==1.6.10"
15 - DJANGO="django==1.8.4"
16 - DJANGO="django==1.7.10"
17 - DJANGO="django==1.6.11"
1418 - DJANGO="django==1.5.12"
1519
1620 matrix:
1721 exclude:
1822 - python: "2.6"
19 env: DJANGO="django==1.7.4"
23 env: DJANGO="django==1.7.10"
24 - python: "2.6"
25 env: DJANGO="django==1.8.4"
2026
2127 branches:
2228 only:
2329 - master
24 - drf-3.0
2530
2631 # command to install requirements
2732 install:
2833 - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install ordereddict; fi
2934 - pip install $DJANGO
3035 - pip install -r requirements-test.txt
31 - python setup.py -q install
36 - python setup.py -q develop
3237
3338 before_script:
3439 - createdb django_restframework_gis
3540 - psql -U postgres -d django_restframework_gis -c "CREATE EXTENSION postgis;"
36 - psql -U postgres -d django_restframework_gis -c "CREATE EXTENSION postgis_topology;"
3741
3842 # command to run tests, e.g. python setup.py test
3943 script:
40 - coverage run --source=rest_framework_gis,django_restframework_gis_tests runtests.py
44 - coverage run --source=rest_framework_gis runtests.py
4145
4246 after_success:
43 coveralls
47 coveralls
77 Christoph Heer https://github.com/jarus
88 Federico Capoano https://github.com/nemesisdesign/
99 Shanto https://github.com/Shanto
10 Eric Theise https://github.com/erictheise
0 Changelog
1 =========
2
3 Version 0.9.5 [2015-10-12]
4 --------------------------
5
6 - `#71 <https://github.com/djangonauts/django-rest-framework-gis/pull/71>`_: added possibility to override GeoJSON properties in ``GeoFeatureModelSerializer``
7 - `52e15a5 <https://github.com/djangonauts/django-rest-framework-gis/commit/52e15a5>`_: Added default ``page_size_query_param`` in ``GeoJsonPagination``
8
9 Version 0.9.4 [2015-09-08]
10 --------------------------
11
12 - `#68 <https://github.com/djangonauts/django-rest-framework-gis/issues/68>`_: ensure not having drf-gis in ``INSTALLED_APPS`` works anyway
13 - `#76 <https://github.com/djangonauts/django-rest-framework-gis/issues/76>`_: avoid pickle errors in ``GeoJsonDict``
14 - `#75 <https://github.com/djangonauts/django-rest-framework-gis/pull/75>`_: return ``GEOSGeometry`` instead of geojson property
15
16 Version 0.9.3 [2015-07-22]
17 --------------------------
18
19 - `04fd1bf <https://github.com/djangonauts/django-rest-framework-gis/commit/04fd1bf>`_: Added ``GeoJsonPagination``
20 - `fe47d86 <https://github.com/djangonauts/django-rest-framework-gis/commit/fe47d86>`_: Improved ``ValidationError`` message of ``GeometryField``
21 - `a3ddd3d <https://github.com/djangonauts/django-rest-framework-gis/commit/a3ddd3d>`_: **Improved serialization performance between 25% and 29%**
22 - `fb6ed36 <https://github.com/djangonauts/django-rest-framework-gis/commit/fb6ed36>`_: ``GeoModelSerializer`` deprecated because obsolete
23 - `#66 <https://github.com/djangonauts/django-rest-framework-gis/pull/66>`_: geometry now allows ``None`` values according to the **GeoJSON spec**
24 - `#67 <https://github.com/djangonauts/django-rest-framework-gis/pull/67>`_: discern ``False`` or empty string values from ``None`` in ``GeoFeatureModelSerializer``
25
26 Version 0.9.2 [2015-07-15]
27 --------------------------
28
29 - `#59 <https://github.com/djangonauts/django-rest-framework-gis/pull/59>`_: Added GeometrySerializerMethodField
30 - `3fa2354 <https://github.com/djangonauts/django-rest-framework-gis/commit/3fa2354>`_: removed broken/obsolete/untested code
31
32 Version 0.9.1 [2015-06-28]
33 --------------------------
34
35 - `#63 <https://github.com/djangonauts/django-rest-framework-gis/issues/63>`_: added compatibility with python 3.2 and updated compatibility table in README
36 - `#60 <https://github.com/djangonauts/django-rest-framework-gis/pull/60>`_: ensure GeoJSON is rendered correctly in browsable API when using python 2
37 - `#62 <https://github.com/djangonauts/django-rest-framework-gis/issues/62>`_: updated django-rest-framework requirement to 3.1.3
38
39 Version 0.9 [2015-05-31]
40 ------------------------
41
42 - `#55 <https://github.com/djangonauts/django-rest-framework-gis/pull/55>`_: Fixed exception in ``DistanceToPointFilter`` in case of invalid point
43 - `#58 <https://github.com/djangonauts/django-rest-framework-gis/pull/58>`_: Fixed handling of ``None`` values in ``GeoFeatureModelSerializer`` to avoid problems with ``FileField`` and ``ImageField``
44 - `#57 <https://github.com/djangonauts/django-rest-framework-gis/pull/57>`_: Added support for GeoJSON Bounding Boxes in ``GeoFeatureModelSerializer``
45
46 Version 0.8.2 [2015-04-29]
47 --------------------------
48
49 - `#53 <https://github.com/djangonauts/django-rest-framework-gis/pull/53>`_: Added support for PATCH requests in ``GeoFeatureModelSerializer``
50
51 Version 0.8.1 [2015-03-25]
52 --------------------------
53
54 - Added compatibility with django-rest-framework 3.1.x
55 - Added compatibility with django 1.8 (RC1)
56
57 Version 0.8 [2015-03-03]
58 ------------------------
59
60 - Added compatibility with django-rest-framework 3.x
61
62 Version 0.7 [2014-10-03]
63 ------------------------
64
65 - upgraded development status classifer to Beta
66 - avoid empty string in textarea widget if value is None
67 - allow field definition in GeoFeatureModelSerializer to be list
68
69 Version 0.6 [2014-09-24]
70 ------------------------
71
72 - Added compatibility to django-rest-framework 2.4.3
73
74 Version 0.5 [2014-09-07]
75 ------------------------
76
77 - added TMSTileFilter
78 - added DistanceToPointFilter
79 - renamed InBBOXFilter to InBBoxFilter
80 - added compatibility with DRF 2.4.0
81
82 Version 0.4 [2014-08-25]
83 ------------------------
84
85 - python3 compatibility
86 - improved DRF browsable API HTML widget (textarea instead of text input)
87
88 Version 0.3 [2014-07-07]
89 ------------------------
90
91 - added compatibility with DRF 2.3.14
92
93 Version 0.2 [2014-03-18]
94 ------------------------
95
96 - geofilter support
97 - README in restructured text for pypi
98 - updated python package info
99
100 Version 0.1 [2013-12-30]
101 ------------------------
102
103 - first release
0 Contributing
1 ============
2
3 Thanks for your interest! We love contributions, so please feel free to fix bugs, improve things, provide documentation. Just `follow the
4 guidelines <https://github.com/djangonauts/django-rest-framework-gis#contributing>`_ and submit a PR.
(No changes)
0 include AUTHORS LICENSE README.md
0 include AUTHORS LICENSE README.rst requirements.txt
11 recursive-include tests *
22 recursive-exclude * *.pyc *.swp
00 django-rest-framework-gis
11 =========================
22
3 |Build Status| |Coverage Status| |Code Health| |Requirements Status| |PyPI version| |PyPI downloads|
3 |Build Status| |Coverage Status| |Requirements Status| |PyPI version| |PyPI downloads|
44
55 Geographic add-ons for Django Rest Framework - `Mailing
66 List <http://bit.ly/1M4sLTp>`__.
1919
2020 pip install https://github.com/djangonauts/django-rest-framework-gis/tarball/master
2121
22 Setup
23 -----
24
25 Add ``rest_framework_gis`` in ``settings.INSTALLED_APPS``, after ``rest_framework``:
26
27 .. code-block:: python
28
29 INSTALLED_APPS = [
30 # ...
31 'rest_framework',
32 'rest_framework_gis',
33 # ...
34 ]
35
2236 Compatibility with DRF, Django and Python
2337 -----------------------------------------
2438
25 =============== ============================ ==================== ==================
39 =============== ============================ ==================== ==================================
2640 DRF-gis version DRF version Django version Python version
27 **0.8** **3.0.4** **1.5.x** to **1.7** **2.6** to **3.4**
28 **0.7** **2.4.3** **1.5.x** to **1.7** **2.6** to **3.4**
29 **0.6** **2.4.3** **1.5.x** to **1.7** **2.6** to **3.4**
30 **0.5** from **2.3.14** to **2.4.2** **1.5.x** to **1.7** **2.6** to **3.4**
31 **0.4** from **2.3.14** to **2.4.2** **1.5.x** to **1.7** **2.6** to **3.4**
41 **0.9.5** **3.1.X** to **3.2.X** **1.5.x** to **1.8** **2.6** to **3.4**
42 **0.9.4** **3.1.X** to **3.2.X** **1.5.x** to **1.8** **2.6** to **3.4**
43 **0.9.3** **3.1.X** **1.5.x** to **1.8** **2.6** to **3.4**
44 **0.9.2** **3.1.X** **1.5.x** to **1.8** **2.6** to **3.4**
45 **0.9.1** **3.1.X** **1.5.x** to **1.8** **2.6** to **3.4**
46 **0.9** **3.1.X** **1.5.x** to **1.8** **2.6**, **2.7**, **3.3**, **3.4**
47 **0.9** **3.1.X** **1.5.x** to **1.8** **2.6**, **2.7**, **3.3**, **3.4**
48 **0.9** **3.1.X** **1.5.x** to **1.8** **2.6**, **2.7**, **3.3**, **3.4**
49 **0.8.2** **3.0.4** to **3.1.1** **1.5.x** to **1.8** **2.6**, **2.7**, **3.3**, **3.4**
50 **0.8.1** **3.0.4** to **3.1.1** **1.5.x** to **1.8** **2.6**, **2.7**, **3.3**, **3.4**
51 **0.8** **3.0.4** **1.5.x** to **1.7** **2.6**, **2.7**, **3.3**, **3.4**
52 **0.7** **2.4.3** **1.5.x** to **1.7** **2.6**, **2.7**, **3.3**, **3.4**
53 **0.6** **2.4.3** **1.5.x** to **1.7** **2.6**, **2.7**, **3.3**, **3.4**
54 **0.5** from **2.3.14** to **2.4.2** **1.5.x** to **1.7** **2.6**, **2.7**, **3.3**, **3.4**
55 **0.4** from **2.3.14** to **2.4.2** **1.5.x** to **1.7** **2.6**, **2.7**, **3.3**, **3.4**
3256 **0.3** from **2.3.14** to **2.4.2** **1.5.x**, **1.6.x** **2.6**, **2.7**
3357 **0.2** from **2.2.2** to **2.3.13** **1.5.x**, **1.6.x** **2.6**, **2.7**
34 =============== ============================ ==================== ==================
58 =============== ============================ ==================== ==================================
3559
3660 Fields
3761 ------
3862
39 Provides a GeometryField, which is a subclass of Django Rest Framework
63 GeometryField
64 ~~~~~~~~~~~~~
65
66 Provides a ``GeometryField``, which is a subclass of Django Rest Framework
4067 (from now on **DRF**) ``WritableField``. This field handles GeoDjango
4168 geometry fields, providing custom ``to_native`` and ``from_native``
4269 methods for GeoJSON input/output.
4370
71 **New in 0.9.3:** there is no need to define this field explicitly in your serializer,
72 it's mapped automatically during initialization in ``rest_framework_gis.apps.AppConfig.ready()``.
73
74 GeometrySerializerMethodField
75 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
76
77 Provides a ``GeometrySerializerMethodField``, which is a subclass of DRF
78 ``SerializerMethodField`` and handles values which are computed with a serializer
79 method and are used as a ``geo_field``. `See example below <https://github.com/djangonauts/django-rest-framework-gis#using-geometryserializermethodfield-as-geo_field>`__.
80
4481 Serializers
4582 -----------
4683
47 GeoModelSerializer
48 ~~~~~~~~~~~~~~~~~~
84 GeoModelSerializer (DEPRECATED)
85 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
86
87 **Deprecated, will be removed in 1.0**: Using this serializer is not needed anymore since 0.9.3 if you add
88 ``rest_framework_gis`` in ``settings.INSTALLED_APPS``
4989
5090 Provides a ``GeoModelSerializer``, which is a sublass of DRF
5191 ``ModelSerializer``. This serializer updates the field\_mapping
95135 GeoFeatureModelSerializer
96136 ~~~~~~~~~~~~~~~~~~~~~~~~~
97137
98 ``GeoFeatureModelSerializer`` is a subclass of ``GeoModelSerializer``
138 ``GeoFeatureModelSerializer`` is a subclass of ``rest_framework.ModelSerializer``
99139 which will output data in a format that is **GeoJSON** compatible. Using
100140 the above example, the ``GeoFeatureModelSerializer`` will output:
101141
119159
120160 If you are serializing an object list, ``GeoFeatureModelSerializer``
121161 will create a ``FeatureCollection``:
122
123 (**NOTE:** This currenty does not work with the default pagination
124 serializer)
125162
126163 .. code-block:: javascript
127164
160197 }
161198 }
162199
163 ``GeoFeatureModelSerializer`` requires you to define a **``geo_field``**
200 Specifying the geometry field: "geo_field"
201 ##########################################
202
203 ``GeoFeatureModelSerializer`` requires you to define a ``geo_field``
164204 to be serialized as the "geometry". For example:
165205
166206 .. code-block:: python
178218 # as with a ModelSerializer.
179219 fields = ('id', 'address', 'city', 'state')
180220
221 Using GeometrySerializerMethodField as "geo_field"
222 ##################################################
223
224 ``geo_field`` may also be an instance of ``GeometrySerializerMethodField``.
225 In this case you can compute its value during serialization. For example:
226
227 .. code-block:: python
228
229 from django.contrib.gis.geos import Point
230 from rest_framework_gis.serializers import GeoFeatureModelSerializer, GeometrySerializerMethodField
231
232 class LocationSerializer(GeoFeatureModelSerializer):
233 """ A class to serialize locations as GeoJSON compatible data """
234
235 # a field which contains a geometry value and can be used as geo_field
236 other_point = GeometrySerializerMethodField()
237
238 def get_other_point(self, obj):
239 return Point(obj.point.lat / 2, obj.point.lon / 2)
240
241 class Meta:
242 model = Location
243 geo_field = 'other_point'
244
245 Serializer for ``geo_field`` may also return ``None`` value, which will translate to ``null`` value for geojson ``geometry`` field.
246
247 Specifying the ID: "id_field"
248 #############################
249
181250 The primary key of the model (usually the "id" attribute) is
182251 automatically put outside the "properties" object (before "type") unless
183 **``id_field``** is set to False:
252 ``id_field`` is set to False:
184253
185254 .. code-block:: python
186255
194263 id_field = False
195264 fields = ('id', 'address', 'city', 'state')
196265
197 You could also set the **``id_field``** to some other unique field in
266 You could also set the ``id_field`` to some other unique field in
198267 your model, like **"slug"**:
199268
200269 .. code-block:: python
205274
206275 class Meta:
207276 model = Location
208 geo_field = "point"
209 id_field = "slug"
277 geo_field = 'point'
278 id_field = 'slug'
210279 fields = ('slug', 'address', 'city', 'state')
280
281 Bounding Box: "auto_bbox" and "bbox_geo_field"
282 ##############################################
283
284 The GeoJSON specification allows a feature to contain a
285 `boundingbox of a feature <http://geojson.org/geojson-spec.html#geojson-objects>`__.
286 ``GeoFeatureModelSerializer`` allows two different ways to fill this property. The first
287 is using the ``geo_field`` to calculate the bounding box of a feature. This only allows
288 read access for a REST client and can be achieved using ``auto_bbox``. Example:
289
290 .. code-block:: python
291
292 from rest_framework_gis.serializers import GeoFeatureModelSerializer
293
294 class LocationSerializer(GeoFeatureModelSerializer):
295 class Meta:
296 model = Location
297 geo_field = 'geometry'
298 auto_bbox = True
299
300
301 The second approach uses the ``bbox_geo_field`` to specify an addional
302 GeometryField of the model which will be used to calculate the bounding box. This allows
303 boundingboxes differ from the exact extent of a features geometry. Additionally this
304 enables read and write access for the REST client. Bounding boxes send from the client will
305 be saved as Polygons. Example:
306
307 .. code-block:: python
308
309 from rest_framework_gis.serializers import GeoFeatureModelSerializer
310
311 class LocationSerializer(GeoFeatureModelSerializer):
312
313 class Meta:
314 model = BoxedLocation
315 geo_field = 'geometry'
316 bbox_geo_field = 'bbox_geometry'
317
318
319 Custom GeoJSON properties source
320 ################################
321
322 In GeoJSON each feature can have a ``properties`` member containing the
323 attributes of the feature. By default this field is filled with the
324 attributes from your Django model, excluding the id, geometry and bounding
325 box fields. It's possible to override this behaviour and implement a custom
326 source for the ``properties`` member.
327
328 The following example shows how to use a PostgreSQL HStore field as a source for
329 the ``properties`` member:
330
331 .. code-block:: python
332
333 # models.py
334 class Link(models.Model):
335 """
336 Metadata is stored in a PostgreSQL HStore field, which allows us to
337 store arbitrary key-value pairs with a link record.
338 """
339 metadata = HStoreField(blank=True, null=True, default={})
340 geo = models.LineStringField()
341 objects = models.GeoManager()
342
343 # serializers.py
344 class NetworkGeoSerializer(GeoFeatureModelSerializer):
345 class Meta:
346 model = models.Link
347 geo_field = 'geo'
348 auto_bbox = True
349
350 def get_properties(self, instance, fields):
351 # This is a PostgreSQL HStore field, which django maps to a dict
352 return instance.metadata
353
354 def unformat_geojson(self, feature):
355 attrs = {
356 self.Meta.geo_field: feature["geometry"],
357 "metadata": feature["properties"]
358 }
359
360 if self.Meta.bbox_geo_field and "bbox" in feature:
361 attrs[self.Meta.bbox_geo_field] = Polygon.from_bbox(feature["bbox"])
362
363 return attrs
364
365 When the serializer renders GeoJSON, it calls the method
366 ``get_properties`` for each object in the database. This function
367 should return a dictionary containing the attributes for the feature. In the
368 case of a HStore field, this function is easily implemented.
369
370 The reverse is also required: mapping a GeoJSON formatted structure to
371 attributes of your model. This task is done by ``unformat_geojson``. It should
372 return a dictionary with your model attributes as keys, and the corresponding
373 values retrieved from the GeoJSON feature data.
374
375 Pagination
376 ----------
377
378 We provide a ``GeoJsonPagination`` class.
379
380 GeoJsonPagination
381 ~~~~~~~~~~~~~~~~~
382
383 Based on ``rest_framework.pagination.PageNumberPagination``.
384
385 Code example:
386
387 .. code-block:: python
388
389 from rest_framework_gis.pagination import GeoJsonPagination
390 # --- other omitted imports --- #
391
392 class GeojsonLocationList(generics.ListCreateAPIView):
393 # -- other omitted view attributes --- #
394 pagination_class = GeoJsonPagination
395
396 Example result response (cut to one element only instead of 10):
397
398 .. code-block:: javascript
399
400 {
401 "type": "FeatureCollection",
402 "count": 25,
403 "next": "http://localhost:8000/geojson/?page=2",
404 "previous": null,
405 "features": [
406 {
407 "type": "Feature",
408 "geometry": {
409 "type": "Point",
410 "coordinates": [
411 42.0,
412 50.0
413 ]
414 },
415 "properties": {
416 "name": "test"
417 }
418 }
419 ]
420 }
421
211422
212423 Filters
213424 -------
401612 8. Document your changes
402613 9. Send pull request
403614
404 .. |Build Status| image:: https://travis-ci.org/djangonauts/django-rest-framework-gis.png?branch=master
615 .. |Build Status| image:: https://travis-ci.org/djangonauts/django-rest-framework-gis.svg?branch=master
405616 :target: https://travis-ci.org/djangonauts/django-rest-framework-gis
406 .. |Coverage Status| image:: https://coveralls.io/repos/djangonauts/django-rest-framework-gis/badge.png
617 .. |Coverage Status| image:: https://coveralls.io/repos/djangonauts/django-rest-framework-gis/badge.svg
407618 :target: https://coveralls.io/r/djangonauts/django-rest-framework-gis
408 .. |Code Health| image:: https://landscape.io/github/djangonauts/django-rest-framework-gis/master/landscape.png
409 :target: https://landscape.io/github/djangonauts/django-rest-framework-gis/master
410 .. |Requirements Status| image:: https://requires.io/github/djangonauts/django-rest-framework-gis/requirements.png?branch=master
619 .. |Requirements Status| image:: https://requires.io/github/djangonauts/django-rest-framework-gis/requirements.svg?branch=master
411620 :target: https://requires.io/github/djangonauts/django-rest-framework-gis/requirements/?branch=master
412 .. |PyPI version| image:: https://badge.fury.io/py/djangorestframework-gis.png
621 .. |PyPI version| image:: https://badge.fury.io/py/djangorestframework-gis.svg
413622 :target: http://badge.fury.io/py/djangorestframework-gis
414 .. |PyPI downloads| image:: https://pypip.in/d/djangorestframework-gis/badge.png
623 .. |PyPI downloads| image:: https://img.shields.io/pypi/dm/djangorestframework-gis.svg
415624 :target: http://badge.fury.io/py/djangorestframework-gis
0 Average of 5 measurements for python2 and python3.
1
2 Launch the performance test with::
3
4 cd tests
5 django test --keepdb django_restframework_gis_tests.test_performance
6
7 For more information regarding how the measurement is performed read the code in
8 `test_performance.py <https://github.com/djangonauts/django-rest-framework-gis/blob/master/tests/django_restframework_gis_tests/test_performance.py>`__.
9
10 0.9.2
11 =====
12
13 - **py2**: 6.304474401472
14 - **py3**: 6.952443096001661
15
16 0.9.3
17 =====
18
19 - **py2**: 4.462462759018 (**29.2%** improvement)
20 - **py3**: 5.217188624000118 (**25%** improvement)
21
22 0.9.4
23 =====
24
25 - **py2**: 4.227859210966
26 - **py3**: 5.00467809599977
27
28 0.9.5
29 =====
30
31 - **py2**: 4.193417596816
32 - **py3**: 4.89978777600045
00 psycopg2
11 djangorestframework>=3.0.4
2 coverage
2 coverage==3.7.1 # rq.filter: >=3,<4
33 coveralls
44 django-filter
5 contexttimer
0 djangorestframework>=3.0.4
0 djangorestframework>=3.0.4,<3.3
0 VERSION = (0, 8, 0, 'final')
0 VERSION = (0, 9, 5, 'final')
11 __version__ = VERSION # alias
22
33
1111 if VERSION[3] != 'final':
1212 version = '%s %s' % (version, VERSION[3])
1313 return version
14
15
16 default_app_config = 'rest_framework_gis.apps.AppConfig'
17
18 # maintain support for django 1.5 and 1.6
19 # TODO: remove in version 1.0
20 try:
21 import os
22 import django
23
24 if os.environ.get('DJANGO_SETTINGS_MODULE'):
25 from django.conf import settings
26 from .apps import AppConfig
27
28 if 'rest_framework_gis' not in settings.INSTALLED_APPS:
29 import warnings
30 warnings.simplefilter('always', DeprecationWarning)
31 warnings.warn('\nGeoModelSerializer is deprecated, '
32 'add "rest_framework_gis" to settings.INSTALLED_APPS and use '
33 '"rest_framework.ModelSerializer" instead',
34 DeprecationWarning)
35
36 if django.get_version() < '1.7' or 'rest_framework_gis' not in settings.INSTALLED_APPS:
37 import rest_framework_gis
38 AppConfig('rest_framework_gis', rest_framework_gis).ready()
39 except ImportError:
40 pass
0 try:
1 from django.apps import AppConfig as BaseConfig
2 except ImportError: # pragma: nocover
3 # django <= 1.6
4 class BaseConfig(object):
5 def __init__(self, *args):
6 pass
7
8
9 class AppConfig(BaseConfig):
10 name = 'rest_framework_gis'
11
12 def ready(self):
13 """
14 update Django Rest Framework serializer mappings
15 """
16 from django.contrib.gis.db import models
17 from rest_framework.serializers import ModelSerializer
18 from .fields import GeometryField
19
20 try:
21 # drf 3.0
22 field_mapping = ModelSerializer._field_mapping.mapping
23 except AttributeError:
24 # drf 3.1
25 field_mapping = ModelSerializer.serializer_field_mapping
26
27 # map GeoDjango fields to drf-gis GeometryField
28 field_mapping.update({
29 models.GeometryField: GeometryField,
30 models.PointField: GeometryField,
31 models.LineStringField: GeometryField,
32 models.PolygonField: GeometryField,
33 models.MultiPointField: GeometryField,
34 models.MultiLineStringField: GeometryField,
35 models.MultiPolygonField: GeometryField,
36 models.GeometryCollectionField: GeometryField
37 })
0 # rest_framework_gis/fields.py
1
2 try:
3 import simplejson as json
4 except ImportError:
5 import json
0 import json
61
72 from django.contrib.gis.geos import GEOSGeometry, GEOSException
83 from django.contrib.gis.gdal import OGRException
94 from django.core.exceptions import ValidationError
105 from django.utils.translation import ugettext_lazy as _
11 from rest_framework.fields import Field
6 from rest_framework.fields import Field, SerializerMethodField
7
8 from .utils import OrderedDict
9
10
11 __all__ = ['GeometryField', 'GeometrySerializerMethodField']
1212
1313
1414 class GeometryField(Field):
2424 def to_representation(self, value):
2525 if isinstance(value, dict) or value is None:
2626 return value
27
28 # Get GeoDjango geojson serialization and then convert it _back_ to
29 # a Python object
30 return json.loads(GEOSGeometry(value).geojson)
27 # we expect value to be a GEOSGeometry instance
28 return GeoJsonDict((
29 ('type', value.geom_type),
30 ('coordinates', value.coords),
31 ))
3132
3233 def to_internal_value(self, value):
3334 if value == '' or value is None:
3435 return value
35
36 if isinstance(value, GEOSGeometry):
37 # value already has the correct representation
38 return value
3639 if isinstance(value, dict):
3740 value = json.dumps(value)
38
3941 try:
40 return GEOSGeometry(value).geojson
42 return GEOSGeometry(value)
4143 except (ValueError, GEOSException, OGRException, TypeError):
42 raise ValidationError(_('Invalid format: string or unicode input unrecognized as WKT EWKT, and HEXEWKB.'))
44 raise ValidationError(_('Invalid format: string or unicode input unrecognized as GeoJSON, WKT EWKT or HEXEWKB.'))
4345
4446 def validate_empty_values(self, data):
45 if data in [u'']:
47 if data == '':
4648 self.fail('required')
4749 return super(GeometryField, self).validate_empty_values(data)
50
51
52 class GeometrySerializerMethodField(SerializerMethodField):
53 def to_representation(self, value):
54 value = super(GeometrySerializerMethodField, self).to_representation(value)
55 if value is not None:
56 # we expect value to be a GEOSGeometry instance
57 return GeoJsonDict((
58 ('type', value.geom_type),
59 ('coordinates', value.coords),
60 ))
61 else:
62 return None
63
64
65 class GeoJsonDict(OrderedDict):
66 """
67 Used for serializing GIS values to GeoJSON values
68 TODO: remove this when support for python 2.6/2.7 will be dropped
69 """
70
71 def __str__(self):
72 """
73 Avoid displaying strings like
74 ``{ 'type': u'Point', 'coordinates': [12, 32] }``
75 in DRF browsable UI inputs (python 2.6/2.7)
76 see: https://github.com/djangonauts/django-rest-framework-gis/pull/60
77 """
78 return json.dumps(self)
0 from math import cos, pi
1
02 from django.db.models import Q
13 from django.core.exceptions import ImproperlyConfigured
24 from django.contrib.gis.db import models
57
68 from rest_framework.filters import BaseFilterBackend
79 from rest_framework.exceptions import ParseError
8 from math import cos, pi
910
1011 from .tilenames import tile_edges
1112
1213 try:
1314 import django_filters
14 except ImportError:
15 except ImportError: # pragma: no cover
1516 raise ImproperlyConfigured(
1617 'restframework-gis filters depend on package "django-filter" '
1718 'which is missing. Install with "pip install django-filter".'
1819 )
20
21 try: # pragma: no cover
22 # django >= 1.8
23 from django.contrib.gis.db.models.lookups import gis_lookups
24 except ImportError: # pragma: no cover
25 # django <= 1.7
26 gis_lookups = models.sql.query.ALL_TERMS
1927
2028
2129 __all__ = [
3240 bbox_param = 'in_bbox' # The URL query parameter which contains the bbox.
3341
3442 def get_filter_bbox(self, request):
35 bbox_string = request.QUERY_PARAMS.get(self.bbox_param, None)
43 bbox_string = request.query_params.get(self.bbox_param, None)
3644 if not bbox_string:
3745 return None
3846
3947 try:
4048 p1x, p1y, p2x, p2y = (float(n) for n in bbox_string.split(','))
4149 except ValueError:
42 raise ParseError("Not valid bbox string in parameter %s."
43 % self.bbox_param)
50 raise ParseError('Invalid bbox string supplied for parameter {0}'.format(self.bbox_param))
4451
4552 x = Polygon.from_bbox((p1x, p1y, p2x, p2y))
4653 return x
7784
7885 def __new__(cls, *args, **kwargs):
7986 cls.filter_overrides.update(cls.GEOFILTER_FOR_DBFIELD_DEFAULTS)
80 cls.LOOKUP_TYPES = sorted(models.sql.query.ALL_TERMS)
87 cls.LOOKUP_TYPES = sorted(gis_lookups)
8188 return super(GeoFilterSet, cls).__new__(cls)
8289
8390
8491 class TMSTileFilter(InBBoxFilter):
85 tile_param = 'tile' # The URL query paramater which contains the tile address
92 tile_param = 'tile' # The URL query paramater which contains the tile address
8693
8794 def get_filter_bbox(self, request):
88 tile_string = request.QUERY_PARAMS.get(self.tile_param, None)
95 tile_string = request.query_params.get(self.tile_param, None)
8996 if not tile_string:
9097 return None
9198
9299 try:
93100 z, x, y = (int(n) for n in tile_string.split('/'))
94101 except ValueError:
95 raise ParseError("Not valid tile string in parameter %s."
96 % self.tile_param)
102 raise ParseError('Invalid tile string supplied for parameter {0}'.format(self.tile_param))
97103
98104 bbox = Polygon.from_bbox(tile_edges(x, y, z))
99105 return bbox
101107
102108 class DistanceToPointFilter(BaseFilterBackend):
103109 dist_param = 'dist'
104 point_param = 'point' # The URL query parameter which contains the
110 point_param = 'point' # The URL query parameter which contains the
105111
106112 def get_filter_point(self, request):
107 point_string = request.QUERY_PARAMS.get(self.point_param, None)
113 point_string = request.query_params.get(self.point_param, None)
108114 if not point_string:
109115 return None
110116
111117 try:
112 (x,y) = (float(n) for n in point_string.split(','))
118 (x, y) = (float(n) for n in point_string.split(','))
113119 except ValueError:
114 raise ParseError("Not valid geometry string in parameter %s."
115 % self.point_string)
120 raise ParseError('Invalid geometry string supplied for parameter {0}'.format(self.point_param))
116121
117 p = Point(x,y)
122 p = Point(x, y)
118123 return p
119124
120125 def dist_to_deg(self, distance, latitude):
121126 """
122127 distance = distance in meters
123 latitude = latitude in degrees
128 latitude = latitude in degrees
124129
125130 at the equator, the distance of one degree is equal in latitude and longitude.
126131 at higher latitudes, a degree longitude is shorter in length, proportional to cos(latitude)
127132 http://en.wikipedia.org/wiki/Decimal_degrees
128133
129134 This function is part of a distance filter where the database 'distance' is in degrees.
130 There's no good single-valued answer to this problem.
131 The distance/ degree is quite constant N/S around the earth (latitude),
135 There's no good single-valued answer to this problem.
136 The distance/ degree is quite constant N/S around the earth (latitude),
132137 but varies over a huge range E/W (longitude).
133138
134 Split the difference: I'm going to average the the degrees latitude and degrees longitude
135 corresponding to the given distance. At high latitudes, this will be too short N/S
136 and too long E/W. It splits the errors between the two axes.
139 Split the difference: I'm going to average the the degrees latitude and degrees longitude
140 corresponding to the given distance. At high latitudes, this will be too short N/S
141 and too long E/W. It splits the errors between the two axes.
137142
138143 Errors are < 25 percent for latitudes < 60 degrees N/S.
139144 """
140145 # d * (180 / pi) / earthRadius ==> degrees longitude
141146 # (degrees longitude) / cos(latitude) ==> degrees latitude
142 lat = latitude if latitude >= 0 else -1*latitude
143 rad2deg = 180/pi
147 lat = latitude if latitude >= 0 else -1 * latitude
148 rad2deg = 180 / pi
144149 earthRadius = 6378160.0
145150 latitudeCorrection = 0.5 * (1 + cos(lat * pi / 180))
146151 return (distance / (earthRadius * latitudeCorrection) * rad2deg)
147
152
148153 def filter_queryset(self, request, queryset, view):
149154 filter_field = getattr(view, 'distance_filter_field', None)
150155 convert_distance_input = getattr(view, 'distance_filter_convert_meters', False)
158163 return queryset
159164
160165 # distance in meters
161 dist_string = request.QUERY_PARAMS.get(self.dist_param, 1000)
166 dist_string = request.query_params.get(self.dist_param, 1000)
162167 try:
163168 dist = float(dist_string)
164169 except ValueError:
165 raise ParseError("Not valid distance string in parameter %s."
166 % self.dist_param)
170 raise ParseError('Invalid distance string supplied for parameter {0}'.format(self.dist_param))
167171
168 if (convert_distance_input):
172 if (convert_distance_input):
169173 # Warning: assumes that the point is (lon,lat)
170174 dist = self.dist_to_deg(dist, point[1])
171
175
172176 return queryset.filter(Q(**{'%s__%s' % (filter_field, geoDjango_filter): (point, dist)}))
0 from .filters import GeoFilterSet
1
2
3
0 from .filters import GeoFilterSet # noqa
0 from rest_framework import pagination
1 from rest_framework.response import Response
2
3 from .utils import OrderedDict
4
5
6 class GeoJsonPagination(pagination.PageNumberPagination):
7 """
8 A geoJSON implementation of a pagination serializer.
9 """
10 page_size_query_param = 'page_size'
11
12 def get_paginated_response(self, data):
13 return Response(OrderedDict([
14 ('type', 'FeatureCollection'),
15 ('count', self.page.paginator.count),
16 ('next', self.get_next_link()),
17 ('previous', self.get_previous_link()),
18 ('features', data['features'])
19 ]))
+0
-1
rest_framework_gis/parsers.py less more
0 # rest_framework_gis/parsers.py
00 from django.core.exceptions import ImproperlyConfigured
1 from django.contrib.gis.db.models.fields import GeometryField as django_GeometryField
1 from django.contrib.gis.geos import Polygon
22
33 from rest_framework.serializers import ModelSerializer, ListSerializer, LIST_SERIALIZER_KWARGS
4 from rest_framework.utils.field_mapping import ClassLookupDict
54
6 try:
7 from collections import OrderedDict
8 # python 2.6
9 except ImportError:
10 from ordereddict import OrderedDict
11
12 from .fields import GeometryField
13
14 # map drf-gis GeometryField to GeoDjango Geometry Field
15 _geo_field_mapping = ModelSerializer._field_mapping.mapping
16 _geo_field_mapping.update({
17 django_GeometryField: GeometryField
18 })
5 from .fields import GeometryField, GeometrySerializerMethodField # noqa
6 from .utils import OrderedDict
197
208
219 class GeoModelSerializer(ModelSerializer):
2210 """
23 A subclass of DFR ModelSerializer that adds support
24 for GeoDjango fields to be serialized as GeoJSON
25 compatible data
11 Deprecated, will be removed in django-rest-framework-gis 1.0
2612 """
27 _field_mapping = ClassLookupDict(_geo_field_mapping)
2813
2914
3015 class GeoFeatureModelListSerializer(ListSerializer):
4227 ))
4328
4429
45 class GeoFeatureModelSerializer(GeoModelSerializer):
30 class GeoFeatureModelSerializer(ModelSerializer):
4631 """
47 A subclass of GeoModelSerializer
32 A subclass of ModelSerializer
4833 that outputs geojson-ready data as
4934 features and feature collections
5035 """
6348 def __init__(self, *args, **kwargs):
6449 super(GeoFeatureModelSerializer, self).__init__(*args, **kwargs)
6550 self.Meta.id_field = getattr(self.Meta, 'id_field', self.Meta.model._meta.pk.name)
66 if self.Meta.geo_field is None:
51 if not hasattr(self.Meta, 'geo_field') or not self.Meta.geo_field:
6752 raise ImproperlyConfigured("You must define a 'geo_field'.")
68 # make sure geo_field is included in fields
69 if hasattr(self.Meta, 'exclude'):
70 if self.Meta.geo_field in self.Meta.exclude:
71 raise ImproperlyConfigured("You cannot exclude your 'geo_field'.")
72 if hasattr(self.Meta, 'fields'):
73 if self.Meta.geo_field not in self.Meta.fields:
74 if type(self.Meta.fields) is tuple:
75 additional_fields = (self.Meta.geo_field, )
76 else:
77 additional_fields = [self.Meta.geo_field, ]
78 self.Meta.fields += additional_fields
53
54 def check_excludes(field_name, field_role):
55 """make sure the field is not excluded"""
56 if hasattr(self.Meta, 'exclude') and field_name in self.Meta.exclude:
57 raise ImproperlyConfigured("You cannot exclude your '{0}'.".format(field_role))
58
59 def add_to_fields(field_name):
60 """Make sure the field is included in the fields"""
61 if hasattr(self.Meta, 'fields'):
62 if field_name not in self.Meta.fields:
63 if type(self.Meta.fields) is tuple:
64 additional_fields = (field_name,)
65 else:
66 additional_fields = [field_name]
67 self.Meta.fields += additional_fields
68
69 check_excludes(self.Meta.geo_field, 'geo_field')
70 add_to_fields(self.Meta.geo_field)
71
72 self.Meta.bbox_geo_field = getattr(self.Meta, 'bbox_geo_field', None)
73 if self.Meta.bbox_geo_field:
74 check_excludes(self.Meta.bbox_geo_field, 'bbox_geo_field')
75 add_to_fields(self.Meta.bbox_geo_field)
76
77 self.Meta.auto_bbox = getattr(self.Meta, 'auto_bbox', False)
78 if self.Meta.bbox_geo_field and self.Meta.auto_bbox:
79 raise ImproperlyConfigured(
80 "You must eiher define a 'bbox_geo_field' or 'auto_bbox', but you can not set both"
81 )
7982
8083 def to_representation(self, instance):
8184 """
8285 Serialize objects -> primitives.
8386 """
84 ret = OrderedDict()
85 fields = [field for field in self.fields.values() if not field.write_only]
87 # prepare OrderedDict geojson structure
88 feature = OrderedDict()
89 # the list of fields that will be processed by get_properties
90 # we will remove fields that have been already processed
91 # to increase performance on large numbers
92 fields = list(self.fields.values())
8693
87 # geo structure
88 if self.Meta.id_field is not False:
89 ret["id"] = ""
90 ret["type"] = "Feature"
91 ret["geometry"] = {}
92 ret["properties"] = OrderedDict()
94 # optional id attribute
95 if self.Meta.id_field:
96 field = self.fields[self.Meta.id_field]
97 value = field.get_attribute(instance)
98 feature["id"] = field.to_representation(value)
99 fields.remove(field)
100
101 # required type attribute
102 # must be "Feature" according to GeoJSON spec
103 feature["type"] = "Feature"
104
105 # required geometry attribute
106 # MUST be present in output according to GeoJSON spec
107 field = self.fields[self.Meta.geo_field]
108 geo_value = field.get_attribute(instance)
109 feature["geometry"] = field.to_representation(geo_value)
110 fields.remove(field)
111 # Bounding Box
112 # if auto_bbox feature is enabled
113 # bbox will be determined automatically automatically
114 if self.Meta.auto_bbox and geo_value:
115 feature["bbox"] = geo_value.extent
116 # otherwise it can be determined via another field
117 elif self.Meta.bbox_geo_field:
118 field = self.fields[self.Meta.bbox_geo_field]
119 value = field.get_attribute(instance)
120 feature["bbox"] = value.extent if hasattr(value, 'extent') else None
121 fields.remove(field)
122
123 # GeoJSON properties
124 feature["properties"] = self.get_properties(instance, fields)
125
126 return feature
127
128 def get_properties(self, instance, fields):
129 """
130 Get the feature metadata which will be used for the GeoJSON
131 "properties" key.
132
133 By default it returns all serializer fields excluding those used for
134 the ID, the geometry and the bounding box.
135
136 :param instance: The current Django model instance
137 :param fields: The list of fields to process (fields already processed have been removed)
138 :return: OrderedDict containing the properties of the current feature
139 :rtype: OrderedDict
140 """
141 properties = OrderedDict()
93142
94143 for field in fields:
95 field_name = field.field_name
96 if field.read_only and instance is None:
144 if field.write_only:
97145 continue
98146 value = field.get_attribute(instance)
99 if value:
100 value = field.to_representation(value)
101 if self.Meta.id_field is not False and field_name == self.Meta.id_field:
102 ret["id"] = value
103 elif field_name == self.Meta.geo_field:
104 ret["geometry"] = value
105 elif not getattr(field, 'write_only', False):
106 ret["properties"][field_name] = value
147 properties[field.field_name] = field.to_representation(value)
107148
108 return ret
149 return properties
109150
110151 def to_internal_value(self, data):
111152 """
112153 Override the parent method to first remove the GeoJSON formatting
113154 """
114 if 'features' in data:
115 _unformatted_data = []
116 features = data['features']
117 for feature in features:
118 _dict = feature["properties"]
119 geom = { self.Meta.geo_field: feature["geometry"] }
120 _dict.update(geom)
121 _unformatted_data.append(_dict)
122 elif 'properties' in data:
123 _dict = data["properties"]
124 geom = { self.Meta.geo_field: data["geometry"] }
125 _dict.update(geom)
126 _unformatted_data = _dict
127 else:
128 _unformatted_data = data
155 if 'properties' in data:
156 data = self.unformat_geojson(data)
157 return super(GeoFeatureModelSerializer, self).to_internal_value(data)
129158
130 return super(GeoFeatureModelSerializer, self).to_internal_value(_unformatted_data)
159 def unformat_geojson(self, feature):
160 """
161 This function should return a dictionary containing keys which maps
162 to serializer fields.
163
164 Remember that GeoJSON contains a key "properties" which contains the
165 feature metadata. This should be flattened to make sure this
166 metadata is stored in the right serializer fields.
167
168 :param feature: The dictionary containing the feature data directly
169 from the GeoJSON data.
170 :return: A new dictionary which maps the GeoJSON values to
171 serializer fields
172 """
173 attrs = feature["properties"]
174
175 if 'geometry' in feature:
176 attrs[self.Meta.geo_field] = feature['geometry']
177
178 if self.Meta.bbox_geo_field and 'bbox' in feature:
179 attrs[self.Meta.bbox_geo_field] = Polygon.from_bbox(feature['bbox'])
180
181 return attrs
11 # -------------------------------------------------------
22 # Translates between lat/long and the slippy-map tile
33 # numbering scheme
4 #
4 #
55 # http://wiki.openstreetmap.org/index.php/Slippy_map_tilenames
6 #
6 #
77 # Written by Oliver White, 2007
88 # This file is public-domain
99 # -------------------------------------------------------
1111
1212
1313 def num_tiles(z):
14 return(math_pow(2,z))
14 return(math_pow(2, z))
1515
1616
17 def lat_edges(y,z):
17 def lat_edges(y, z):
1818 n = num_tiles(z)
1919 unit = 1 / n
2020 relY1 = y * unit
2121 relY2 = relY1 + unit
2222 lat1 = mercator_to_lat(pi * (1 - 2 * relY1))
2323 lat2 = mercator_to_lat(pi * (1 - 2 * relY2))
24 return(lat1,lat2)
24 return(lat1, lat2)
2525
2626
27 def lon_edges(x,z):
27 def lon_edges(x, z):
2828 n = num_tiles(z)
2929 unit = 360 / n
3030 lon1 = -180 + x * unit
3131 lon2 = lon1 + unit
32 return(lon1,lon2)
32 return(lon1, lon2)
3333
3434
35 def tile_edges(x,y,z):
36 lat1,lat2 = lat_edges(y,z)
37 lon1,lon2 = lon_edges(x,z)
38 return((lon1, lat2, lon2, lat1)) # w, s, e, n
35 def tile_edges(x, y, z):
36 lat1, lat2 = lat_edges(y, z)
37 lon1, lon2 = lon_edges(x, z)
38 return((lon1, lat2, lon2, lat1)) # w, s, e, n
3939
4040
4141 def mercator_to_lat(mercatorY):
0 try:
1 from collections import OrderedDict # noqa
2 # python 2.6
3 except ImportError: # pragma: no cover
4 from ordereddict import OrderedDict # noqa
00 #!/usr/bin/env python
11 # -*- coding: utf-8 -*-
22
3 import os, sys
3 import os
4 import sys
5
46 sys.path.insert(0, "tests")
57 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
68
0 [bdist_wheel]
1 universal=1
0 #!/usr/bin/env python
01 import sys
12 import os
23 from setuptools import setup, find_packages
3 from setuptools.command.test import test
44 from rest_framework_gis import get_version
55
66
1919
2020
2121 if sys.argv[-1] == 'publish':
22 os.system("python setup.py sdist upload")
22 os.system("python setup.py sdist bdist_wheel upload -s")
2323 args = {'version': get_version()}
2424 print("You probably want to also tag the version now:")
2525 print(" git tag -a %(version)s -m 'version %(version)s'" % args)
3232 version=get_version(),
3333 license='BSD',
3434 author='Douglas Meehan',
35 author_email='dmeehan@gmail.com',
35 author_email='django-rest-framework-gis@googlegroups.com',
3636 description='Geographic add-ons for Django Rest Framework',
3737 url='https://github.com/djangonauts/django-rest-framework-gis',
3838 download_url='https://github.com/djangonauts/django-rest-framework-gis/releases',
(No changes)
11 from django.utils.text import slugify
22
33
4 __all__ = ['Location']
4 __all__ = [
5 'Location',
6 'LocatedFile',
7 'BoxedLocation'
8 ]
59
610
7 class Location(models.Model):
11 class BaseModel(models.Model):
812 name = models.CharField(max_length=32)
913 slug = models.SlugField(max_length=128, unique=True, blank=True)
1014 geometry = models.GeometryField()
11
1215 objects = models.GeoManager()
13
16
17 class Meta:
18 abstract = True
19
1420 def __unicode__(self):
1521 return self.name
16
22
1723 def _generate_slug(self):
1824 if self.slug == '' or self.slug is None:
1925 try:
2127 except NameError:
2228 name = self.name
2329 self.slug = slugify(name)
24
30
2531 def clean(self):
2632 self._generate_slug()
27
33
2834 def save(self, *args, **kwargs):
2935 self._generate_slug()
30 super(Location, self).save(*args, **kwargs)
36 super(BaseModel, self).save(*args, **kwargs)
37
38
39 class Location(BaseModel):
40 pass
41
42
43 class LocatedFile(BaseModel):
44 file = models.FileField(upload_to='located_files', blank=True, null=True)
45
46
47 class BoxedLocation(BaseModel):
48 bbox_geometry = models.PolygonField()
0 from django.contrib.gis.geos import Point
1
02 from rest_framework import pagination, serializers
13 from rest_framework_gis import serializers as gis_serializers
24
911 'LocationGeoFeatureSerializer',
1012 'LocationGeoFeatureSlugSerializer',
1113 'LocationGeoFeatureFalseIDSerializer',
12 'PaginatedLocationGeoFeatureSerializer',
14 'LocatedFileGeoFeatureSerializer',
15 'BoxedLocationGeoFeatureSerializer',
16 'LocationGeoFeatureBboxSerializer',
17 'LocationGeoFeatureMethodSerializer',
18 'NoneGeoFeatureMethodSerializer',
1319 ]
1420
1521
16 class LocationGeoSerializer(gis_serializers.GeoModelSerializer):
22 class LocationGeoSerializer(serializers.ModelSerializer):
1723 """ location geo serializer """
18
1924 details = serializers.HyperlinkedIdentityField(view_name='api_location_details')
2025
2126 class Meta:
2227 model = Location
23 geo_field = 'geometry'
2428
2529
26 class PaginatedLocationGeoSerializer(pagination.PaginationSerializer):
27
28 class Meta:
29 object_serializer_class = LocationGeoSerializer
30 class PaginatedLocationGeoSerializer(pagination.PageNumberPagination):
31 page_size_query_param = 'limit'
32 page_size = 40
33 max_page_size = 10000
3034
3135
3236 class LocationGeoFeatureSerializer(gis_serializers.GeoFeatureModelSerializer):
3337 """ location geo serializer """
34
3538 details = serializers.HyperlinkedIdentityField(view_name='api_geojson_location_details')
3639 fancy_name = serializers.SerializerMethodField()
3740
3841 def get_fancy_name(self, obj):
39 return u'Kool %s' % obj.name
42 return 'Kool %s' % obj.name
4043
4144 class Meta:
4245 model = Location
4548
4649 class LocationGeoFeatureSlugSerializer(LocationGeoFeatureSerializer):
4750 """ use slug as id attribute """
48
4951 class Meta:
5052 model = Location
5153 geo_field = 'geometry'
5254 id_field = 'slug'
53 fields = ['name', 'slug']
55 fields = ('name', 'slug')
5456
5557
5658 class LocationGeoFeatureFalseIDSerializer(LocationGeoFeatureSerializer):
5759 """ id attribute set as False """
58
5960 class Meta:
6061 model = Location
6162 geo_field = 'geometry'
6263 id_field = False
6364
6465
65 class PaginatedLocationGeoFeatureSerializer(pagination.PaginationSerializer):
66 class LocatedFileGeoFeatureSerializer(gis_serializers.GeoFeatureModelSerializer):
67 """ located file geo serializer """
68 details = serializers.HyperlinkedIdentityField(view_name='api_geojson_located_file_details')
69 fancy_name = serializers.SerializerMethodField()
70 file = serializers.FileField(allow_empty_file=True)
71
72 def get_fancy_name(self, obj):
73 return 'Nice %s' % obj.name
6674
6775 class Meta:
68 object_serializer_class = LocationGeoFeatureSerializer
76 model = Location
77 geo_field = 'geometry'
78
79
80 class BoxedLocationGeoFeatureSerializer(gis_serializers.GeoFeatureModelSerializer):
81 """ location geo serializer """
82 details = serializers.HyperlinkedIdentityField(view_name='api_geojson_boxedlocation_details')
83
84 class Meta:
85 model = BoxedLocation
86 geo_field = 'geometry'
87 bbox_geo_field = 'bbox_geometry'
88 fields = ['name', 'details', 'id']
89
90
91 class LocationGeoFeatureBboxSerializer(gis_serializers.GeoFeatureModelSerializer):
92 class Meta:
93 model = Location
94 geo_field = 'geometry'
95 auto_bbox = True
96
97
98 class LocationGeoFeatureMethodSerializer(gis_serializers.GeoFeatureModelSerializer):
99 new_geometry = gis_serializers.GeometrySerializerMethodField()
100
101 def get_new_geometry(self, obj):
102 if obj.name.startswith('hidden'):
103 return Point(0., 0.)
104 else:
105 return obj.geometry
106
107 class Meta:
108 model = Location
109 geo_field = 'new_geometry'
110
111
112 class NoneGeoFeatureMethodSerializer(gis_serializers.GeoFeatureModelSerializer):
113 new_geometry = gis_serializers.GeometrySerializerMethodField()
114
115 def get_new_geometry(self, obj):
116 return None
117
118 class Meta:
119 model = Location
120 geo_field = 'new_geometry'
121 fields = ['name', 'slug', 'id']
0 import json
1
2 from django.test import TestCase
3 from django.core.urlresolvers import reverse
4 from django.core.exceptions import ImproperlyConfigured
5
6 from rest_framework_gis import serializers as gis_serializers
7
8 from .models import BoxedLocation, Location
9 from .serializers import LocationGeoSerializer
10
11
12 class TestRestFrameworkGisBBox(TestCase):
13 """
14 unit tests for bbox support in restframework_gis
15 """
16 def setUp(self):
17 self.geojson_boxedlocation_list_url = reverse('api_geojson_boxedlocation_list')
18 self.geojson_location_bbox_list_url = reverse('api_geojson_location_bbox_list')
19
20 def _create_locations(self):
21 self.bl1 = BoxedLocation.objects.create(id=1, name='l1', slug='l1', geometry='POINT (13.007 42.423)',
22 bbox_geometry='POLYGON((12.997 42.413,12.997 42.433,13.017 42.433,13.017 42.413,12.997 42.413))')
23 self.bl2 = BoxedLocation.objects.create(id=2, name='l2', slug='l2', geometry='POINT (12.007 43.423)',
24 bbox_geometry='POLYGON((11.997 43.413,11.997 43.433,12.017 43.433,12.017 43.413,11.997 43.413))')
25 self.l1 = Location.objects.create(id=1, name='l1', slug='l1',
26 geometry='POLYGON((12.997 42.413,12.997 42.433,13.017 42.433,13.017 42.413,12.997 42.413))')
27
28 def test_list(self):
29 self._create_locations()
30 response = self.client.get(self.geojson_boxedlocation_list_url)
31 self.assertEqual(response.status_code, 200)
32 self.assertEqual(len(response.data['features']), 2)
33 for feature in response.data['features']:
34 self.assertIn('bbox', feature)
35 fid = feature['id']
36 if fid==1:
37 self.assertEqual(feature['bbox'], self.bl1.bbox_geometry.extent)
38 elif fid==2:
39 self.assertEqual(feature['bbox'], self.bl2.bbox_geometry.extent)
40 else:
41 self.fail("Unexpected id: {0}".format(fid))
42 BoxedLocation.objects.all().delete()
43
44 def test_post_location_list_geojson(self):
45 self.assertEqual(BoxedLocation.objects.count(), 0)
46 data = {
47 "properties": {
48 "name": "geojson input test",
49 },
50 "geometry": {
51 "type": "Point",
52 "coordinates": [
53 12.49,
54 41.89
55 ]
56 },
57 "bbox": [11.0, 40.0, 13.0, 42.0]
58 }
59 response = self.client.post(self.geojson_boxedlocation_list_url, data=json.dumps(data), content_type='application/json')
60 self.assertEqual(response.status_code, 201)
61 self.assertEqual(BoxedLocation.objects.count(), 1)
62 self.assertEqual(BoxedLocation.objects.all()[0].bbox_geometry.extent, (11.0,40.0,13.0,42.0))
63
64 def test_get_autogenerated_location_bbox_geojson(self):
65 self._create_locations()
66 response = self.client.get(self.geojson_location_bbox_list_url)
67 self.assertEqual(response.status_code, 200)
68 self.assertEqual(len(response.data['features']), 1)
69 self.assertEqual(response.data['features'][0]['bbox'], self.l1.geometry.extent)
70
71 def test_bbox_improperly_configured(self):
72 self._create_locations()
73 class LocationGeoFeatureSerializer(gis_serializers.GeoFeatureModelSerializer):
74 class Meta:
75 model = Location
76 geo_field = 'geometry'
77 bbox_geo_field = 'geometry'
78 auto_bbox = True
79 with self.assertRaises(ImproperlyConfigured):
80 LocationGeoFeatureSerializer(instance=self.l1)
0 """
1 unit tests for restframework_gis
2 """
3
4 try:
5 import simplejson as json
6 except ImportError:
7 import json
8
9
0 import json
101 import urllib
2
113 from django.test import TestCase
124 from django.contrib.gis.geos import GEOSGeometry, Polygon
135 from django.core.urlresolvers import reverse
157 from .models import Location
168
179
18 class TestRestFrameworkGis(TestCase):
19
10 class TestRestFrameworkGisFilters(TestCase):
11 """
12 unit tests for filters feature in restframework_gis
13 """
2014 def setUp(self):
2115 self.location_contained_in_bbox_list_url = reverse('api_geojson_location_list_contained_in_bbox_filter')
2216 self.location_overlaps_bbox_list_url = reverse('api_geojson_location_list_overlaps_bbox_filter')
133127 self.assertEqual(Location.objects.count(), 0)
134128
135129 # Filter parameters
136 distance = 5000 #meters
130 distance = 5000 # meters
137131 point_inside_ggpark = [-122.49034881591797, 37.76949349270407]
138132 point_on_golden_gate_bridge = [-122.47894, 37.8199]
139133 point_on_alcatraz = [-122.4222, 37.82667]
140134 point_on_treasure_island = [-122.3692, 37.8244]
141135 point_on_angel_island = [-122.4326, 37.86091]
136
137 url_params = '?dist=%0.4f&point=hello&format=json' % (distance)
138 response = self.client.get('%s%s' % (self.location_within_distance_of_point_list_url, url_params))
139 self.assertEqual(response.status_code, 400)
142140
143141 url_params = '?dist=%0.4f&point=%0.4f,%0.4f&format=json' % (distance, point_on_alcatraz[0], point_on_alcatraz[1])
144142
369367
370368 url_params = "?contains_properly=%s" % quoted_param
371369
372 response = self.client.get(self.geojson_contained_in_geometry + url_params)
370 response = self.client.get('{0}{1}'.format(self.geojson_contained_in_geometry, url_params))
373371 self.assertEqual(len(response.data), 1)
374372
375373 geometry_response = GEOSGeometry(json.dumps(response.data[0]['geometry']))
380378 # try without any param, should return both
381379 response = self.client.get(self.geojson_contained_in_geometry)
382380 self.assertEqual(len(response.data), 2)
381
382 def test_inBBOXFilter_filtering_none(self):
383 url_params = '?in_bbox=&format=json'
384 response = self.client.get(self.location_contained_in_bbox_list_url + url_params)
385 self.assertDictEqual(response.data, {'type':'FeatureCollection','features':[]})
386
387 def test_inBBOXFilter_ValueError(self):
388 url_params = '?in_bbox=0&format=json'
389 response = self.client.get(self.location_contained_in_bbox_list_url + url_params)
390 self.assertEqual(response.data['detail'], 'Invalid bbox string supplied for parameter in_bbox')
391
392 def test_inBBOXFilter_filter_field_none(self):
393 from .views import GeojsonLocationContainedInBBoxList as view
394 original_value = view.bbox_filter_field
395 view.bbox_filter_field = None
396 url_params = '?in_bbox=0,0,0,0&format=json'
397 response = self.client.get(self.location_contained_in_bbox_list_url + url_params)
398 self.assertDictEqual(response.data, {'type':'FeatureCollection','features':[]})
399 view.bbox_filter_field = original_value
400
401 def test_TileFilter_filtering_none(self):
402 url_params = '?tile=&format=json'
403 response = self.client.get(self.location_contained_in_tile_list_url + url_params)
404 self.assertEqual(response.data, {'type':'FeatureCollection','features':[]})
405
406 def test_TileFilter_ValueError(self):
407 url_params = '?tile=1/0&format=json'
408 response = self.client.get(self.location_contained_in_tile_list_url + url_params)
409 self.assertEqual(response.data['detail'], 'Invalid tile string supplied for parameter tile')
410
411 def test_DistanceToPointFilter_filtering_none(self):
412 url_params = '?dist=%0.4f&point=&format=json' % 5000
413 response = self.client.get('%s%s' % (self.location_within_distance_of_point_list_url, url_params))
414 self.assertDictEqual(response.data, {'type':'FeatureCollection','features':[]})
415
416 def test_DistanceToPointFilter_filter_field_none(self):
417 from .views import GeojsonLocationWithinDistanceOfPointList as view
418 original_value = view.distance_filter_field
419 view.distance_filter_field = None
420 url_params = '?dist=%0.4f&point=&format=json' % 5000
421 response = self.client.get('%s%s' % (self.location_within_distance_of_point_list_url, url_params))
422 self.assertDictEqual(response.data, {'type':'FeatureCollection','features':[]})
423 view.distance_filter_field = original_value
424
425 def test_DistanceToPointFilter_ValueError_point(self):
426 url_params = '?dist=500.0&point=hello&format=json'
427 response = self.client.get('%s%s' % (self.location_within_distance_of_point_list_url, url_params))
428 self.assertEqual(response.data['detail'], 'Invalid geometry string supplied for parameter point')
429
430 def test_DistanceToPointFilter_ValueError_distance(self):
431 url_params = '?dist=wrong&point=12.0,42.0&format=json'
432 response = self.client.get('%s%s' % (self.location_within_distance_of_point_list_url, url_params))
433 self.assertEqual(response.data['detail'], 'Invalid distance string supplied for parameter dist')
0 import sys
1 from django.conf import settings
2
3 # this test must be run explicitly
4 # either by calling:
5 # django test --keepdb django_restframework_gis_tests.test_performance
6 # or by setting ``settings.TEST_PERFORMANCE`` to ``True``
7 if 'django_restframework_gis_tests.test_performance' in sys.argv or settings.TEST_PERFORMANCE:
8 from django.test import TestCase
9 from django.core.urlresolvers import reverse
10 from rest_framework.renderers import JSONRenderer
11 from rest_framework_gis import serializers as gis_serializers
12 from contexttimer import Timer
13 from .models import Location
14
15
16 class TestRestFrameworkGisPerformance(TestCase):
17 NUMBER_OF_LOCATIONS = 10000
18
19 def _create_data(self):
20 """ creates a bunch of gis models instances """
21 locations = []
22 name = 'l{0}'
23 slug = 'l{0}'
24 wkt = 'POINT (13.{0}125000020002 42.{0}565179379999)'
25 for n in range(1, self.NUMBER_OF_LOCATIONS):
26 locations.append(Location(name=name.format(n),
27 slug=slug.format(n),
28 geometry=wkt.format(n)))
29 Location.objects.bulk_create(locations)
30
31 def test_geojson_performance(self):
32 class PerfSerializer(gis_serializers.GeoFeatureModelSerializer):
33 class Meta:
34 model = Location
35 geo_field = 'geometry'
36 # create data
37 self._create_data()
38 # initialize serializer
39 serializer = PerfSerializer(Location.objects.all(), many=True)
40 with Timer() as t:
41 JSONRenderer().render(serializer.data)
42 # print results
43 msg = 'GeoJSON rendering of {0} objects '\
44 'completed in {1}'.format(self.NUMBER_OF_LOCATIONS, t.elapsed)
45 print('\n\033[95m{0}\033[0m'.format(msg))
11 unit tests for restframework_gis
22 """
33
4 try:
5 import simplejson as json
6 except ImportError:
7 import json
8
9
104 import urllib
115 import sys
6 import json
7
128 from django.test import TestCase
13 from django.contrib.gis.geos import GEOSGeometry, Polygon
9 from django.contrib.gis.geos import GEOSGeometry, Polygon, Point
1410 from django.core.urlresolvers import reverse
15
16 from .models import Location
11 from django.core.exceptions import ImproperlyConfigured
12
13 from rest_framework_gis import serializers as gis_serializers
14
15 from .models import Location, LocatedFile
16 from .serializers import LocationGeoSerializer
1717
1818
1919 class TestRestFrameworkGis(TestCase):
20
2120 def setUp(self):
2221 self.location_list_url = reverse('api_location_list')
2322 self.geojson_location_list_url = reverse('api_geojson_location_list')
24 self.geos_error_message = 'Invalid format: string or unicode input unrecognized as WKT EWKT, and HEXEWKB.'
23 self.geos_error_message = 'Invalid format: string or unicode input unrecognized as GeoJSON, WKT EWKT or HEXEWKB.'
2524
2625 def _create_locations(self):
2726 self.l1 = Location.objects.create(id=1, name='l1', slug='l1', geometry='POINT (13.0078125000020002 42.4234565179379999)')
3332
3433 def test_post_location_list_geojson(self):
3534 self.assertEqual(Location.objects.count(), 0)
36
3735 data = {
3836 "name": "geojson input test",
3937 "geometry": {
4947 ]
5048 }
5149 }
52
53 response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json')
54 self.assertEqual(response.status_code, 201)
55 self.assertEqual(Location.objects.count(), 1)
56
50 response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json')
51 self.assertEqual(response.status_code, 201)
52 self.assertEqual(Location.objects.count(), 1)
5753 data = {
5854 "name": "geojson input test2",
5955 "geometry": {
7167 def test_post_location_list_geojson_as_multipartformdata(self):
7268 """ emulate sending geojson string in webform """
7369 self.assertEqual(Location.objects.count(), 0)
74
7570 data = {
7671 "name": "geojson input test",
7772 "geometry": json.dumps({
8782 ]
8883 })
8984 }
90
9185 response = self.client.post(self.location_list_url, data)
9286 self.assertEqual(response.status_code, 201)
9387 self.assertEqual(Location.objects.count(), 1)
9488
9589 def test_post_HTML_browsable_api(self):
9690 self.assertEqual(Location.objects.count(), 0)
97
9891 data = {
9992 "name": "geojson input test2",
10093 "slug": "prova",
114107 response = self.client.post(self.location_list_url, data, HTTP_ACCEPT='text/html')
115108 self.assertEqual(response.status_code, 201)
116109 self.assertEqual(Location.objects.count(), 1)
117
118110 location = Location.objects.all()[0]
119111 self.assertEqual(location.name, 'geojson input test2')
120112 self.assertEqual(location.slug, 'prova')
121113
122114 def test_post_location_list_WKT(self):
123115 self.assertEqual(Location.objects.count(), 0)
124
125116 data = {
126117 'name': 'WKT input test',
127118 'geometry': 'POINT (12.492324113849 41.890307434153)'
130121 self.assertEqual(response.status_code, 201)
131122 self.assertEqual(Location.objects.count(), 1)
132123
124 def test_post_location_list_EWKT(self):
125 self.assertEqual(Location.objects.count(), 0)
126 data = {
127 'name': 'EWKT input test',
128 'geometry': 'SRID=28992;POINT(221160 600204)'
129 }
130 response = self.client.post(self.location_list_url, data)
131 expected_coords = (6.381495826183805, 53.384066927384985)
132 self.assertEqual(response.status_code, 201)
133 self.assertEqual(Location.objects.count(), 1)
134 self.assertEquals(
135 Location.objects.get(name='EWKT input test').geometry.coords,
136 expected_coords
137 )
138
133139 def test_post_location_list_WKT_as_json(self):
134140 self.assertEqual(Location.objects.count(), 0)
135
136141 data = {
137142 'name': 'WKT input test',
138143 'geometry': 'POINT (12.492324113849 41.890307434153)'
144149 def test_post_location_list_empty_geometry(self):
145150 data = { 'name': 'empty input test' }
146151 response = self.client.post(self.location_list_url, data)
147 self.assertEqual(response.data['geometry'][0], u'This field is required.')
148
152 self.assertEqual(response.data['geometry'][0], 'This field is required.')
149153 data = { 'name': 'empty input test', 'geometry': '' }
150154 response = self.client.post(self.location_list_url, data)
151 self.assertEqual(response.data['geometry'][0], u'This field is required.')
152
155 self.assertEqual(response.data['geometry'][0], 'This field is required.')
153156 data = { 'name': 'empty input test' }
154157 response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json')
155 self.assertEqual(response.data['geometry'][0], u'This field is required.')
156
158 self.assertEqual(response.data['geometry'][0], 'This field is required.')
157159 data = { 'name': 'empty input test', 'geometry': '' }
158160 response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json')
159 self.assertEqual(response.data['geometry'][0], u'This field is required.')
161 self.assertEqual(response.data['geometry'][0], 'This field is required.')
160162
161163 def test_post_location_list_invalid_WKT(self):
162164 data = {
167169 self.assertEqual(response.status_code, 400)
168170 self.assertEqual(Location.objects.count(), 0)
169171 self.assertEqual(response.data['geometry'][0], self.geos_error_message)
170
171172 # repeat as multipart form data
172173 response = self.client.post(self.location_list_url, data)
173174 self.assertEqual(response.data['geometry'][0], self.geos_error_message)
174
175175 data = {
176176 'name': 'I AM MODERATELY WRONG',
177177 'geometry': 'POINT (12.492324113849, 41.890307434153)'
178178 }
179179 response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json')
180180 self.assertEqual(response.data['geometry'][0], self.geos_error_message)
181
182181 # repeat as multipart form data
183182 response = self.client.post(self.location_list_url, data)
184183 self.assertEqual(response.data['geometry'][0], self.geos_error_message)
198197 }
199198 response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json')
200199 self.assertEqual(response.data['geometry'][0], self.geos_error_message)
201
202200 data = {
203201 "name": "very wrong",
204202 "geometry": ['a', 'b', 'c']
205203 }
206204 response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json')
207205 self.assertEqual(response.data['geometry'][0], self.geos_error_message)
208
209206 data = {
210207 "name": "very wrong",
211208 "geometry": False
212209 }
213210 response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json')
214211 self.assertEqual(response.data['geometry'][0], self.geos_error_message)
215
216212 data = {
217213 "name": "very wrong",
218214 "geometry": { "value": { "nested": ["yo"] } }
223219 def test_geojson_format(self):
224220 """ test geojson format """
225221 location = Location.objects.create(name='geojson test', geometry='POINT (135.0 45.0)')
226
227222 url = reverse('api_geojson_location_details', args=[location.id])
228223 expected = {
229224 'id': location.id,
247242 self.assertCountEqual(json.dumps(response.data), json.dumps(expected))
248243 else:
249244 self.assertItemsEqual(json.dumps(response.data), json.dumps(expected))
250
251245 response = self.client.get(url, HTTP_ACCEPT='text/html')
252246 self.assertContains(response, "Kool geojson test")
253247
254248 def test_geojson_id_attribute(self):
255249 location = Location.objects.create(name='geojson test', geometry='POINT (10.1 10.1)')
256
257250 url = reverse('api_geojson_location_details', args=[location.id])
258251 response = self.client.get(url)
259252 self.assertEqual(response.data['id'], location.id)
260253
261254 def test_geojson_id_attribute_slug(self):
262255 location = Location.objects.create(name='geojson test', geometry='POINT (10.1 10.1)')
263
264256 url = reverse('api_geojson_location_slug_details', args=[location.slug])
265257 response = self.client.get(url)
266258 self.assertEqual(response.data['id'], location.slug)
267259
268260 def test_geojson_false_id_attribute_slug(self):
269261 location = Location.objects.create(name='geojson test', geometry='POINT (10.1 10.1)')
270
271262 url = reverse('api_geojson_location_falseid_details', args=[location.id])
272263 response = self.client.get(url)
273264 with self.assertRaises(KeyError):
274265 response.data['id']
275266
267 def test_geojson_filefield_attribute(self):
268 located_file = LocatedFile.objects.create(name='geojson filefield test', geometry='POINT (10.1 10.1)')
269 url = reverse('api_geojson_located_file_details', args=[located_file.id])
270 response = self.client.get(url)
271 self.assertEqual(response.data['properties']['file'], None)
272
276273 def test_post_geojson_location_list(self):
277274 self.assertEqual(Location.objects.count(), 0)
278
279275 data = {
280276 "type": "Feature",
281277 "properties": {
290286 ]
291287 }
292288 }
293
294289 response = self.client.post(self.geojson_location_list_url, data=json.dumps(data), content_type='application/json')
295290 self.assertEqual(response.status_code, 201)
296291 self.assertEqual(Location.objects.count(), 1)
297
298292 url = reverse('api_geojson_location_details', args=[Location.objects.order_by('-id')[0].id])
299293 response = self.client.get(url)
300294 self.assertEqual(response.data['properties']['name'], "point?")
304298
305299 def test_post_geojson_location_list_HTML(self):
306300 self.assertEqual(Location.objects.count(), 0)
307
308301 data = {
309302 "type": "Feature",
310303 "properties": {
319312 ]
320313 }
321314 }
322
323315 response = self.client.post(self.geojson_location_list_url, data=json.dumps(data), content_type='application/json', HTTP_ACCEPT='text/html')
324316 self.assertEqual(response.status_code, 201)
325317 self.assertEqual(Location.objects.count(), 1)
326
327318 url = reverse('api_geojson_location_details', args=[Location.objects.order_by('-id')[0].id])
328319 response = self.client.get(url)
329320 self.assertEqual(response.data['properties']['name'], "point?")
345336 ]
346337 }
347338 }
348
349339 response = self.client.post(self.geojson_location_list_url, data=json.dumps(data), content_type='application/json')
350340 self.assertEqual(response.status_code, 400)
351341 self.assertEqual(Location.objects.count(), 0)
352342 self.assertEqual(response.data['name'][0], "This field is required.")
353
354343 data = {
355344 "type": "Feature",
356345 "properties": {
368357
369358 def test_post_geojson_location_list_WKT(self):
370359 self.assertEqual(Location.objects.count(), 0)
371
372360 data = {
373361 "type": "Feature",
374362 "properties": {
376364 },
377365 "geometry": "POINT (10.1 10.1)"
378366 }
379
380367 response = self.client.post(self.geojson_location_list_url, data=json.dumps(data), content_type='application/json')
381368 self.assertEqual(response.status_code, 201)
382369 self.assertEqual(Location.objects.count(), 1)
383
384370 url = reverse('api_geojson_location_details', args=[Location.objects.order_by('-id')[0].id])
385371 response = self.client.get(url)
386372 self.assertEqual(response.data['properties']['name'], "point?")
389375
390376 def test_geofeatured_model_serializer_compatible_with_geomodel_serializer(self):
391377 self.assertEqual(Location.objects.count(), 0)
392
393378 data = {
394379 "name": "geojson input test",
395380 "geometry": {
405390 ]
406391 }
407392 }
408
409393 response = self.client.post(self.geojson_location_list_url, data=json.dumps(data), content_type='application/json')
410394 self.assertEqual(response.status_code, 201)
411395 self.assertEqual(Location.objects.count(), 1)
413397 def test_geofeatured_model_post_as_multipartformdata(self):
414398 """ emulate sending geojson string in webform """
415399 self.assertEqual(Location.objects.count(), 0)
416
417400 data = {
418401 "name": "geojson input test",
419402 "geometry": json.dumps({
424407 ]
425408 })
426409 }
427
428410 response = self.client.post(self.location_list_url, data)
429411 self.assertEqual(response.status_code, 201)
430412 self.assertEqual(Location.objects.count(), 1)
433415 def test_HTML_browsable_geojson_location_list(self):
434416 response = self.client.get(self.geojson_location_list_url, HTTP_ACCEPT='text/html')
435417 self.assertEqual(response.status_code, 200)
436
437418 self._create_locations()
438
439419 response = self.client.get(self.geojson_location_list_url, HTTP_ACCEPT='text/html')
440420 self.assertContains(response, 'l1')
441421 self.assertContains(response, 'l2')
442422
443423 def test_post_geojson_location_list_HTML_web_form(self):
444424 self.assertEqual(Location.objects.count(), 0)
445
446425 data = {
447426 "name": "HTML test",
448427 "geometry": json.dumps({
453432 ]
454433 })
455434 }
456
457435 response = self.client.post(self.geojson_location_list_url, data, HTTP_ACCEPT='text/html')
458436 self.assertEqual(response.status_code, 201)
459437 self.assertEqual(Location.objects.count(), 1)
460
461438 location = Location.objects.all()[0]
462439 self.assertEqual(location.name, "HTML test")
463440 self.assertEqual(location.geometry.geom_type, "Point")
464441
465442 def test_post_geojson_location_list_HTML_web_form_WKT(self):
466443 self.assertEqual(Location.objects.count(), 0)
467
468444 data = {
469445 "name": "HTML test WKT",
470446 "geometry": "POINT (10.1 10.1)"
471447 }
472
473448 response = self.client.post(self.geojson_location_list_url, data, HTTP_ACCEPT='text/html')
474 #print response.content
475 self.assertEqual(response.status_code, 201)
476 self.assertEqual(Location.objects.count(), 1)
477
449 self.assertEqual(response.status_code, 201)
450 self.assertEqual(Location.objects.count(), 1)
478451 location = Location.objects.all()[0]
479452 self.assertEqual(location.name, "HTML test WKT")
480453
484457 self.assertContains(response, '<textarea name="geometry"')
485458 self.assertContains(response, '&quot;type&quot;: &quot;Point&quot;')
486459 self.assertContains(response, '&quot;coordinates&quot;: [')
487 # textarea input should contain valid GeoJSON indented for readability
488 # doesn't seem possible with DRF 3.0
489 # let's wait for DRF 3.1
490 #self.assertNotContains(response, 'u&#39;type&#39;: u&#39;Point&#39;, u&#39;coordinates&#39;:')
460 # TODO: remove this when python 2 will be deprecated
461 self.assertNotContains(response, 'u&#39;type&#39;: u&#39;Point&#39;, u&#39;coordinates&#39;:')
462
463 def test_patch_geojson_location(self):
464 location = Location.objects.create(name='geojson patch test', geometry='POINT (135.0 45.0)')
465 url = reverse('api_geojson_location_details', args=[location.id])
466 data = {
467 "properties": {
468 "name":"geojson successful patch test"
469 },
470 "geometry": {
471 "type": "Point",
472 "coordinates": [10.1, 10.1]
473 }
474 }
475 response = self.client.generic('PATCH', url, json.dumps(data), content_type='application/json')
476 self.assertEqual(response.status_code, 200)
477 location_reloaded = Location.objects.get(pk=location.id)
478 self.assertEquals(location_reloaded.name, 'geojson successful patch test')
479 self.assertEquals(location_reloaded.geometry, Point(10.1, 10.1))
480
481 def test_patch_geojson_location_wo_changing_geometry(self):
482 location = Location.objects.create(name='geojson patch test', geometry='POINT (135.0 45.0)')
483 url = reverse('api_geojson_location_details', args=[location.id])
484 data = {
485 "properties": {
486 "name":"geojson successful patch test"
487 }
488 }
489 response = self.client.generic('PATCH', url, json.dumps(data), content_type='application/json')
490 self.assertEqual(response.status_code, 200)
491 location_reloaded = Location.objects.get(pk=location.id)
492 self.assertEquals(location_reloaded.name, 'geojson successful patch test')
493 self.assertEquals(location_reloaded.geometry, Point(135.0, 45.0))
494
495 def test_geometry_serializer_method_field(self):
496 location = Location.objects.create(name='geometry serializer method test', geometry='POINT (135.0 45.0)')
497 location_loaded = Location.objects.get(pk=location.id)
498 self.assertEqual(location_loaded.name, 'geometry serializer method test')
499 self.assertEqual(location_loaded.geometry, Point(135.0, 45.0))
500 url = reverse('api_geojson_location_details_hidden', args=[location.id])
501 data = {
502 "properties": {
503 "name":"hidden geometry"
504 }
505 }
506 response = self.client.generic('PATCH', url, json.dumps(data), content_type='application/json')
507 self.assertEqual(response.status_code, 200)
508 self.assertEqual(response.data['properties']['name'], 'hidden geometry')
509 self.assertEqual(response.data['geometry']['type'], 'Point')
510 self.assertEqual(response.data['geometry']['coordinates'], (0.0, 0.0))
511
512 def test_geometry_serializer_method_field_none(self):
513 location = Location.objects.create(name='None value', geometry='POINT (135.0 45.0)')
514 location_loaded = Location.objects.get(pk=location.id)
515 self.assertEqual(location_loaded.geometry, Point(135.0, 45.0))
516 url = reverse('api_geojson_location_details_none', args=[location.id])
517 response = self.client.generic('GET', url, content_type='application/json')
518 self.assertEqual(response.status_code, 200)
519 self.assertEqual(response.data['properties']['name'], 'None value')
520 self.assertEqual(response.data['geometry'], None)
521
522 def test_filterset(self):
523 from rest_framework_gis.filterset import GeoFilterSet
524
525 def test_geometry_field_to_representation_none(self):
526 self._create_locations()
527 f = LocationGeoSerializer(instance=self.l1).fields['geometry']
528 self.assertIsNone(f.to_representation(None))
529
530 def test_geometry_field_to_internal_value_none(self):
531 self._create_locations()
532 f = LocationGeoSerializer(instance=self.l1).fields['geometry']
533 self.assertIsNone(f.to_internal_value(None))
534
535 def test_no_geo_field_improperly_configured(self):
536 class LocationGeoFeatureSerializer(gis_serializers.GeoFeatureModelSerializer):
537 class Meta:
538 model = Location
539 with self.assertRaises(ImproperlyConfigured):
540 LocationGeoFeatureSerializer()
541
542 def test_exclude_geo_field_improperly_configured(self):
543 self._create_locations()
544 class LocationGeoFeatureSerializer(gis_serializers.GeoFeatureModelSerializer):
545 class Meta:
546 model = Location
547 geo_field = 'geometry'
548 exclude = ('geometry', )
549 with self.assertRaises(ImproperlyConfigured):
550 LocationGeoFeatureSerializer(instance=self.l1)
551
552 def test_geojson_pagination(self):
553 self._create_locations()
554 response = self.client.get(self.geojson_location_list_url)
555 self.assertEqual(response.data['type'], 'FeatureCollection')
556 self.assertEqual(len(response.data['features']), 2)
557 response = self.client.get('{0}?page_size=1'.format(self.geojson_location_list_url))
558 self.assertEqual(response.data['type'], 'FeatureCollection')
559 self.assertEqual(len(response.data['features']), 1)
560 self.assertIn('next', response.data)
561 self.assertIn('previous', response.data)
562
563 def test_pickle(self):
564 import pickle
565 from rest_framework_gis.fields import GeoJsonDict
566 geometry = GEOSGeometry('POINT (30 10)')
567 geojsondict = GeoJsonDict((
568 ('type', geometry.geom_type),
569 ('coordinates', geometry.coords),
570 ))
571 pickled = pickle.dumps(geojsondict)
572 restored = pickle.loads(pickled)
573 self.assertEqual(restored, geojsondict)
33 urlpatterns = patterns('django_restframework_gis_tests.views',
44 url(r'^$', 'location_list', name='api_location_list'),
55 url(r'^(?P<pk>[0-9]+)/$', 'location_details', name='api_location_details'),
6
6
77 # geojson
88 url(r'^geojson/$', 'geojson_location_list', name='api_geojson_location_list'),
99 url(r'^geojson/(?P<pk>[0-9]+)/$', 'geojson_location_details', name='api_geojson_location_details'),
10 url(r'^geojson_hidden/(?P<pk>[0-9]+)/$', 'geojson_location_details_hidden', name='api_geojson_location_details_hidden'),
11 url(r'^geojson_none/(?P<pk>[0-9]+)/$', 'geojson_location_details_none', name='api_geojson_location_details_none'),
1012 url(r'^geojson/(?P<slug>[-\w]+)/$', 'geojson_location_slug_details', name='api_geojson_location_slug_details'),
1113 url(r'^geojson-falseid/(?P<pk>[0-9]+)/$', 'geojson_location_falseid_details', name='api_geojson_location_falseid_details'),
14
15 # file
16 url(r'^geojson-file/(?P<pk>[0-9]+)/$', 'geojson_located_file_details', name='api_geojson_located_file_details'),
17
18 # geojson with bbox with its own geometry field
19 url(r'^geojson-with-bbox/$', 'geojson_boxedlocation_list', name='api_geojson_boxedlocation_list'),
20 url(r'^geojson-with-bbox/(?P<pk>[0-9]+)/$', 'geojson_boxedlocation_details', name='api_geojson_boxedlocation_details'),
21
22 # geojson with bbox with autogenerated bbox
23 url(r'^geojson-with-auto-bbox/$', 'geojson_location_bbox_list', name='api_geojson_location_bbox_list'),
1224
1325 # Filters
1426 url(r'^filters/contained_in_bbox$', 'geojson_location_contained_in_bbox_list', name='api_geojson_location_list_contained_in_bbox_filter'),
00 from rest_framework import generics
11 from rest_framework.filters import DjangoFilterBackend
2 from rest_framework_gis.filters import *
3 from rest_framework_gis.pagination import GeoJsonPagination
24
35 from .models import *
46 from .serializers import *
5 from rest_framework_gis.filters import *
67
78
89 class LocationList(generics.ListCreateAPIView):
910 model = Location
1011 serializer_class = LocationGeoSerializer
1112 queryset = Location.objects.all()
12 pagination_serializer_class = PaginatedLocationGeoSerializer
13 paginate_by_param = 'limit'
14 paginate_by = 40
15
13 pagination_class = PaginatedLocationGeoSerializer
14
1615 location_list = LocationList.as_view()
17
18
16
17
1918 class LocationDetails(generics.RetrieveUpdateDestroyAPIView):
2019 model = Location
2120 serializer_class = LocationGeoSerializer
2827 model = Location
2928 serializer_class = LocationGeoFeatureSerializer
3029 queryset = Location.objects.all()
31 pagination_serializer_class = PaginatedLocationGeoFeatureSerializer
32 paginate_by_param = 'limit'
33 paginate_by = 40
34
30 pagination_class = GeoJsonPagination
31
3532 geojson_location_list = GeojsonLocationList.as_view()
3633
3734
9289 geojson_location_details = GeojsonLocationDetails.as_view()
9390
9491
92 class GeojsonLocationDetailsHidden(generics.RetrieveUpdateDestroyAPIView):
93 model = Location
94 serializer_class = LocationGeoFeatureMethodSerializer
95 queryset = Location.objects.all()
96
97 geojson_location_details_hidden = GeojsonLocationDetailsHidden.as_view()
98
99
100 class GeojsonLocationDetailsNone(generics.RetrieveUpdateDestroyAPIView):
101 model = Location
102 serializer_class = NoneGeoFeatureMethodSerializer
103 queryset = Location.objects.all()
104
105 geojson_location_details_none = GeojsonLocationDetailsNone.as_view()
106
107
95108 class GeojsonLocationSlugDetails(generics.RetrieveUpdateDestroyAPIView):
96109 model = Location
97110 lookup_field = 'slug'
115128 class Meta:
116129 model = Location
117130
118
119131 class GeojsonLocationContainedInGeometry(generics.ListAPIView):
120132 queryset = Location.objects.all()
121133 serializer_class = LocationGeoSerializer
124136 filter_backends = (DjangoFilterBackend,)
125137
126138 geojson_contained_in_geometry = GeojsonLocationContainedInGeometry.as_view()
139
140
141 class GeojsonLocatedFileDetails(generics.RetrieveUpdateDestroyAPIView):
142 model = LocatedFile
143 serializer_class = LocatedFileGeoFeatureSerializer
144 queryset = LocatedFile.objects.all()
145
146 geojson_located_file_details = GeojsonLocatedFileDetails.as_view()
147
148
149 class GeojsonBoxedLocationDetails(generics.RetrieveUpdateDestroyAPIView):
150 model = BoxedLocation
151 serializer_class = BoxedLocationGeoFeatureSerializer
152 queryset = BoxedLocation.objects.all()
153
154 geojson_boxedlocation_details = GeojsonBoxedLocationDetails.as_view()
155
156
157 class GeojsonBoxedLocationList(generics.ListCreateAPIView):
158 model = BoxedLocation
159 serializer_class = BoxedLocationGeoFeatureSerializer
160 queryset = BoxedLocation.objects.all()
161
162 geojson_boxedlocation_list = GeojsonBoxedLocationList.as_view()
163
164
165 class GeojsonLocationBboxList(generics.ListCreateAPIView):
166 model = Location
167 serializer_class = LocationGeoFeatureBboxSerializer
168 queryset = Location.objects.all()
169
170 geojson_location_bbox_list = GeojsonLocationBboxList.as_view()
0 from settings import *
0 # RENAME THIS FILE TO local_settings.py IF YOU NEED TO CUSTOMIZE SOME SETTINGS
1 # BUT DO NOT COMMIT
12
2 # RENAME THIS FILE local_settings.py IF YOU NEED TO CUSTOMIZE SOME SETTINGS
3 # BUT DO NOT COMMIT
3 TEST_PERFORMANCE = True
44
55 #DATABASES = {
66 # 'default': {
2020 # 'django.contrib.messages',
2121 # 'django.contrib.staticfiles',
2222 # 'django.contrib.gis',
23 #
23 #
2424 # # geodjango widgets
2525 # 'olwidget',
26 #
26 #
2727 # # admin
2828 # #'grappelli',
2929 # 'django.contrib.admin',
30 #
30 #
3131 # # rest framework
3232 # 'rest_framework',
3333 # 'rest_framework_gis',
34 #
34 #
3535 # # test app
3636 # 'django_restframework_gis_tests'
37 #)
37 #)
00 import os
1
2 TEST_PERFORMANCE = False
13
24 DEBUG = True
35 TEMPLATE_DEBUG = DEBUG
3133 'django_restframework_gis_tests'
3234 )
3335
36 MIDDLEWARE_CLASSES = [
37 'django.middleware.common.CommonMiddleware',
38 'django.middleware.csrf.CsrfViewMiddleware',
39 ]
40
3441 ROOT_URLCONF = 'urls'
3542
3643 TIME_ZONE = 'Europe/Rome'
(No changes)