New upstream version 0.14
Michael Fladischer
5 years ago
0 | 0 | Changelog |
1 | 1 | ========= |
2 | ||
3 | Version 0.14.0 [2018-12-02] | |
4 | --------------------------- | |
5 | ||
6 | - `#173 <https://github.com/djangonauts/django-rest-framework-gis/pull/173>`_: | |
7 | added support for django 2.1, DRF 3.9 and switched to django-filters >= 2.0 | |
8 | (**which requires python >= 3.4**) | |
9 | - `#178 <https://github.com/djangonauts/django-rest-framework-gis/pull/178>`_: | |
10 | simplified ``setup.py`` and tox build | |
2 | 11 | |
3 | 12 | Version 0.13.0 [2018-04-27] |
4 | 13 | --------------------------- |
1 | 1 | include LICENSE |
2 | 2 | include README.rst |
3 | 3 | include CHANGES.rst |
4 | include requirements.txt | |
5 | include runtests.py | |
6 | 4 | recursive-include tests *.py |
0 | 0 | Metadata-Version: 1.1 |
1 | 1 | Name: djangorestframework-gis |
2 | Version: 0.13 | |
2 | Version: 0.14 | |
3 | 3 | Summary: Geographic add-ons for Django Rest Framework |
4 | 4 | Home-page: https://github.com/djangonauts/django-rest-framework-gis |
5 | 5 | Author: Douglas Meehan |
6 | 6 | Author-email: django-rest-framework-gis@googlegroups.com |
7 | 7 | License: BSD |
8 | 8 | Download-URL: https://github.com/djangonauts/django-rest-framework-gis/releases |
9 | Description: UNKNOWN | |
9 | Project-URL: Bug Reports, https://github.com/djangonauts/django-rest-framework-gis/issues | |
10 | Project-URL: Source Code, https://github.com/djangonauts/django-rest-framework-gis | |
11 | Project-URL: Code Coverage, https://coveralls.io/github/djangonauts/django-rest-framework-gis | |
12 | Project-URL: Mailing List, https://groups.google.com/forum/#!forum/django-rest-framework-gis | |
13 | Project-URL: Continuous Integration, https://travis-ci.org/djangonauts/django-rest-framework-gis | |
14 | Description: django-rest-framework-gis | |
15 | ========================= | |
16 | ||
17 | |Build Status| |Coverage Status| |Requirements Status| |PyPI version| | |
18 | ||
19 | Geographic add-ons for Django Rest Framework - `Mailing | |
20 | List <http://bit.ly/1M4sLTp>`__. | |
21 | ||
22 | Install last stable version from pypi | |
23 | ------------------------------------- | |
24 | ||
25 | .. code-block:: bash | |
26 | ||
27 | pip install djangorestframework-gis | |
28 | ||
29 | Install development version | |
30 | --------------------------- | |
31 | ||
32 | .. code-block:: bash | |
33 | ||
34 | pip install https://github.com/djangonauts/django-rest-framework-gis/tarball/master | |
35 | ||
36 | Setup | |
37 | ----- | |
38 | ||
39 | Add ``rest_framework_gis`` in ``settings.INSTALLED_APPS``, after ``rest_framework``: | |
40 | ||
41 | .. code-block:: python | |
42 | ||
43 | INSTALLED_APPS = [ | |
44 | # ... | |
45 | 'rest_framework', | |
46 | 'rest_framework_gis', | |
47 | # ... | |
48 | ] | |
49 | ||
50 | Compatibility with DRF, Django and Python | |
51 | ----------------------------------------- | |
52 | ||
53 | =============== ============================ ==================== ================================== | |
54 | DRF-gis version DRF version Django version Python version | |
55 | **0.14.x** **3.3** to **3.9** **1.11** to **2.1** **3.4** to **3.7** | |
56 | **0.13.x** **3.3** to **3.8** **1.11** to **2.0** **2.7** to **3.6** | |
57 | **0.12.x** **3.1** to **3.7** **1.11** to **2.0** **2.7** to **3.6** | |
58 | **0.11.x** **3.1** to **3.6** **1.7** to **1.11** **2.7** to **3.6** | |
59 | **0.10.x** **3.1** to **3.3** **1.7** to **1.9** **2.7** to **3.5** | |
60 | **0.9.6** **3.1** to **3.2** **1.5** to **1.8** **2.6** to **3.5** | |
61 | **0.9.5** **3.1** to **3.2** **1.5** to **1.8** **2.6** to **3.4** | |
62 | **0.9.4** **3.1** to **3.2** **1.5** to **1.8** **2.6** to **3.4** | |
63 | **0.9.3** **3.1** **1.5** to **1.8** **2.6** to **3.4** | |
64 | **0.9.2** **3.1** **1.5** to **1.8** **2.6** to **3.4** | |
65 | **0.9.1** **3.1** **1.5** to **1.8** **2.6** to **3.4** | |
66 | **0.9** **3.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** | |
67 | **0.9** **3.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** | |
68 | **0.9** **3.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** | |
69 | **0.8.2** **3.0.4** to **3.1.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** | |
70 | **0.8.1** **3.0.4** to **3.1.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** | |
71 | **0.8** **3.0.4** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** | |
72 | **0.7** **2.4.3** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** | |
73 | **0.6** **2.4.3** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** | |
74 | **0.5** from **2.3.14** to **2.4.2** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** | |
75 | **0.4** from **2.3.14** to **2.4.2** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** | |
76 | **0.3** from **2.3.14** to **2.4.2** **1.5**, **1.6** **2.6**, **2.7** | |
77 | **0.2** from **2.2.2** to **2.3.13** **1.5**, **1.6** **2.6**, **2.7** | |
78 | =============== ============================ ==================== ================================== | |
79 | ||
80 | Fields | |
81 | ------ | |
82 | ||
83 | GeometryField | |
84 | ~~~~~~~~~~~~~ | |
85 | ||
86 | Provides a ``GeometryField``, which is a subclass of Django Rest Framework | |
87 | (from now on **DRF**) ``WritableField``. This field handles GeoDjango | |
88 | geometry fields, providing custom ``to_native`` and ``from_native`` | |
89 | methods for GeoJSON input/output. | |
90 | ||
91 | This field takes two optional arguments: | |
92 | ||
93 | ``precision``: Passes coordinates through Python's builtin ``round()`` function (`docs | |
94 | <https://docs.python.org/3/library/functions.html#round>`_), rounding values to | |
95 | the provided level of precision. E.g. A Point with lat/lng of | |
96 | ``[51.0486, -114.0708]`` passed through a ``GeometryField(precision=2)`` | |
97 | would return a Point with a lat/lng of ``[51.05, -114.07]``. | |
98 | ||
99 | ``remove_duplicates``: Remove sequential duplicate coordinates from line and | |
100 | polygon geometries. This is particularly useful when used with the ``precision`` | |
101 | argument, as the likelihood of duplicate coordinates increase as precision of | |
102 | coordinates are reduced. | |
103 | ||
104 | **Note:** While both above arguments are designed to reduce the | |
105 | byte size of the API response, they will also increase the processing time | |
106 | required to render the response. This will likely be negligible for small GeoJSON | |
107 | responses but may become an issue for large responses. | |
108 | ||
109 | **New in 0.9.3:** there is no need to define this field explicitly in your serializer, | |
110 | it's mapped automatically during initialization in ``rest_framework_gis.apps.AppConfig.ready()``. | |
111 | ||
112 | GeometrySerializerMethodField | |
113 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
114 | ||
115 | Provides a ``GeometrySerializerMethodField``, which is a subclass of DRF | |
116 | ``SerializerMethodField`` and handles values which are computed with a serializer | |
117 | method and are used as a ``geo_field``. `See example below <https://github.com/djangonauts/django-rest-framework-gis#using-geometryserializermethodfield-as-geo_field>`__. | |
118 | ||
119 | Serializers | |
120 | ----------- | |
121 | ||
122 | GeoModelSerializer (DEPRECATED) | |
123 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
124 | ||
125 | **Deprecated, will be removed in 1.0**: Using this serializer is not needed anymore since 0.9.3 if you add | |
126 | ``rest_framework_gis`` in ``settings.INSTALLED_APPS`` | |
127 | ||
128 | Provides a ``GeoModelSerializer``, which is a subclass of DRF | |
129 | ``ModelSerializer``. This serializer updates the field\_mapping | |
130 | dictionary to include field mapping of GeoDjango geometry fields to the | |
131 | above ``GeometryField``. | |
132 | ||
133 | For example, the following model: | |
134 | ||
135 | .. code-block:: python | |
136 | ||
137 | class Location(models.Model): | |
138 | """ | |
139 | A model which holds information about a particular location | |
140 | """ | |
141 | address = models.Charfield(max_length=255) | |
142 | city = models.CharField(max_length=100) | |
143 | state = models.CharField(max_length=100) | |
144 | point = models.PointField() | |
145 | ||
146 | By default, the DRF ModelSerializer will output: | |
147 | ||
148 | .. code-block:: javascript | |
149 | ||
150 | { | |
151 | "id": 1, | |
152 | "address": "742 Evergreen Terrace", | |
153 | "city": "Springfield", | |
154 | "state": "Oregon", | |
155 | "point": "POINT(-123.0208 44.0464)" | |
156 | } | |
157 | ||
158 | In contrast, the ``GeoModelSerializer`` will output: | |
159 | ||
160 | .. code-block:: javascript | |
161 | ||
162 | { | |
163 | "id": 1, | |
164 | "address": "742 Evergreen Terrace", | |
165 | "city": "Springfield", | |
166 | "state": "Oregon", | |
167 | "point": { | |
168 | "type": "Point", | |
169 | "coordinates": [-123.0208, 44.0464], | |
170 | } | |
171 | } | |
172 | ||
173 | GeoFeatureModelSerializer | |
174 | ~~~~~~~~~~~~~~~~~~~~~~~~~ | |
175 | ||
176 | ``GeoFeatureModelSerializer`` is a subclass of ``rest_framework.ModelSerializer`` | |
177 | which will output data in a format that is **GeoJSON** compatible. Using | |
178 | the above example, the ``GeoFeatureModelSerializer`` will output: | |
179 | ||
180 | .. code-block:: javascript | |
181 | ||
182 | { | |
183 | "id": 1, | |
184 | "type": "Feature", | |
185 | "geometry": { | |
186 | "point": { | |
187 | "type": "Point", | |
188 | "coordinates": [-123.0208, 44.0464], | |
189 | }, | |
190 | }, | |
191 | "properties": { | |
192 | "address": "742 Evergreen Terrace", | |
193 | "city": "Springfield", | |
194 | "state": "Oregon" | |
195 | } | |
196 | } | |
197 | ||
198 | If you are serializing an object list, ``GeoFeatureModelSerializer`` | |
199 | will create a ``FeatureCollection``: | |
200 | ||
201 | .. code-block:: javascript | |
202 | ||
203 | { | |
204 | "type": "FeatureCollection", | |
205 | "features": [ | |
206 | { | |
207 | "id": 1 | |
208 | "type": "Feature", | |
209 | "geometry": { | |
210 | "point": { | |
211 | "type": "Point", | |
212 | "coordinates": [-123.0208, 44.0464], | |
213 | } | |
214 | }, | |
215 | "properties": { | |
216 | "address": "742 Evergreen Terrace", | |
217 | "city": "Springfield", | |
218 | "state": "Oregon", | |
219 | } | |
220 | } | |
221 | { | |
222 | "id": 2, | |
223 | "type": "Feature", | |
224 | "geometry": { | |
225 | "point": { | |
226 | "type": "Point", | |
227 | "coordinates": [-123.0208, 44.0489], | |
228 | }, | |
229 | }, | |
230 | "properties": { | |
231 | "address": "744 Evergreen Terrace", | |
232 | "city": "Springfield", | |
233 | "state": "Oregon" | |
234 | } | |
235 | } | |
236 | } | |
237 | ||
238 | Specifying the geometry field: "geo_field" | |
239 | ########################################## | |
240 | ||
241 | ``GeoFeatureModelSerializer`` requires you to define a ``geo_field`` | |
242 | to be serialized as the "geometry". For example: | |
243 | ||
244 | .. code-block:: python | |
245 | ||
246 | from rest_framework_gis.serializers import GeoFeatureModelSerializer | |
247 | ||
248 | class LocationSerializer(GeoFeatureModelSerializer): | |
249 | """ A class to serialize locations as GeoJSON compatible data """ | |
250 | ||
251 | class Meta: | |
252 | model = Location | |
253 | geo_field = "point" | |
254 | ||
255 | # you can also explicitly declare which fields you want to include | |
256 | # as with a ModelSerializer. | |
257 | fields = ('id', 'address', 'city', 'state') | |
258 | ||
259 | Using GeometrySerializerMethodField as "geo_field" | |
260 | ################################################## | |
261 | ||
262 | ``geo_field`` may also be an instance of ``GeometrySerializerMethodField``. | |
263 | In this case you can compute its value during serialization. For example: | |
264 | ||
265 | .. code-block:: python | |
266 | ||
267 | from django.contrib.gis.geos import Point | |
268 | from rest_framework_gis.serializers import GeoFeatureModelSerializer, GeometrySerializerMethodField | |
269 | ||
270 | class LocationSerializer(GeoFeatureModelSerializer): | |
271 | """ A class to serialize locations as GeoJSON compatible data """ | |
272 | ||
273 | # a field which contains a geometry value and can be used as geo_field | |
274 | other_point = GeometrySerializerMethodField() | |
275 | ||
276 | def get_other_point(self, obj): | |
277 | return Point(obj.point.lat / 2, obj.point.lon / 2) | |
278 | ||
279 | class Meta: | |
280 | model = Location | |
281 | geo_field = 'other_point' | |
282 | ||
283 | Serializer for ``geo_field`` may also return ``None`` value, which will translate to ``null`` value for geojson ``geometry`` field. | |
284 | ||
285 | Specifying the ID: "id_field" | |
286 | ############################# | |
287 | ||
288 | The primary key of the model (usually the "id" attribute) is | |
289 | automatically used as the ``id`` field of each | |
290 | `GeoJSON Feature Object <https://tools.ietf.org/html/draft-butler-geojson#section-2.2>`_. | |
291 | ||
292 | The default behaviour follows the `GeoJSON RFC <https://tools.ietf.org/html/draft-butler-geojson>`_, | |
293 | but it can be disbaled by setting ``id_field`` to ``False``: | |
294 | ||
295 | .. code-block:: python | |
296 | ||
297 | from rest_framework_gis.serializers import GeoFeatureModelSerializer | |
298 | ||
299 | class LocationSerializer(GeoFeatureModelSerializer): | |
300 | ||
301 | class Meta: | |
302 | model = Location | |
303 | geo_field = "point" | |
304 | id_field = False | |
305 | fields = ('id', 'address', 'city', 'state') | |
306 | ||
307 | The ``id_field`` can also be set to use some other unique field in your model, eg: ``slug``: | |
308 | ||
309 | .. code-block:: python | |
310 | ||
311 | from rest_framework_gis.serializers import GeoFeatureModelSerializer | |
312 | ||
313 | class LocationSerializer(GeoFeatureModelSerializer): | |
314 | ||
315 | class Meta: | |
316 | model = Location | |
317 | geo_field = 'point' | |
318 | id_field = 'slug' | |
319 | fields = ('slug', 'address', 'city', 'state') | |
320 | ||
321 | Bounding Box: "auto_bbox" and "bbox_geo_field" | |
322 | ############################################## | |
323 | ||
324 | The GeoJSON specification allows a feature to contain a | |
325 | `boundingbox of a feature <http://geojson.org/geojson-spec.html#geojson-objects>`__. | |
326 | ``GeoFeatureModelSerializer`` allows two different ways to fill this property. The first | |
327 | is using the ``geo_field`` to calculate the bounding box of a feature. This only allows | |
328 | read access for a REST client and can be achieved using ``auto_bbox``. Example: | |
329 | ||
330 | .. code-block:: python | |
331 | ||
332 | from rest_framework_gis.serializers import GeoFeatureModelSerializer | |
333 | ||
334 | class LocationSerializer(GeoFeatureModelSerializer): | |
335 | class Meta: | |
336 | model = Location | |
337 | geo_field = 'geometry' | |
338 | auto_bbox = True | |
339 | ||
340 | ||
341 | The second approach uses the ``bbox_geo_field`` to specify an additional | |
342 | ``GeometryField`` of the model which will be used to calculate the bounding box. This allows | |
343 | boundingboxes differ from the exact extent of a features geometry. Additionally this | |
344 | enables read and write access for the REST client. Bounding boxes send from the client will | |
345 | be saved as Polygons. Example: | |
346 | ||
347 | .. code-block:: python | |
348 | ||
349 | from rest_framework_gis.serializers import GeoFeatureModelSerializer | |
350 | ||
351 | class LocationSerializer(GeoFeatureModelSerializer): | |
352 | ||
353 | class Meta: | |
354 | model = BoxedLocation | |
355 | geo_field = 'geometry' | |
356 | bbox_geo_field = 'bbox_geometry' | |
357 | ||
358 | ||
359 | Custom GeoJSON properties source | |
360 | ################################ | |
361 | ||
362 | In GeoJSON each feature can have a ``properties`` member containing the | |
363 | attributes of the feature. By default this field is filled with the | |
364 | attributes from your Django model, excluding the id, geometry and bounding | |
365 | box fields. It's possible to override this behaviour and implement a custom | |
366 | source for the ``properties`` member. | |
367 | ||
368 | The following example shows how to use a PostgreSQL HStore field as a source for | |
369 | the ``properties`` member: | |
370 | ||
371 | .. code-block:: python | |
372 | ||
373 | # models.py | |
374 | class Link(models.Model): | |
375 | """ | |
376 | Metadata is stored in a PostgreSQL HStore field, which allows us to | |
377 | store arbitrary key-value pairs with a link record. | |
378 | """ | |
379 | metadata = HStoreField(blank=True, null=True, default=dict) | |
380 | geo = models.LineStringField() | |
381 | objects = models.GeoManager() | |
382 | ||
383 | # serializers.py | |
384 | class NetworkGeoSerializer(GeoFeatureModelSerializer): | |
385 | class Meta: | |
386 | model = models.Link | |
387 | geo_field = 'geo' | |
388 | auto_bbox = True | |
389 | ||
390 | def get_properties(self, instance, fields): | |
391 | # This is a PostgreSQL HStore field, which django maps to a dict | |
392 | return instance.metadata | |
393 | ||
394 | def unformat_geojson(self, feature): | |
395 | attrs = { | |
396 | self.Meta.geo_field: feature["geometry"], | |
397 | "metadata": feature["properties"] | |
398 | } | |
399 | ||
400 | if self.Meta.bbox_geo_field and "bbox" in feature: | |
401 | attrs[self.Meta.bbox_geo_field] = Polygon.from_bbox(feature["bbox"]) | |
402 | ||
403 | return attrs | |
404 | ||
405 | When the serializer renders GeoJSON, it calls the method | |
406 | ``get_properties`` for each object in the database. This function | |
407 | should return a dictionary containing the attributes for the feature. In the | |
408 | case of a HStore field, this function is easily implemented. | |
409 | ||
410 | The reverse is also required: mapping a GeoJSON formatted structure to | |
411 | attributes of your model. This task is done by ``unformat_geojson``. It should | |
412 | return a dictionary with your model attributes as keys, and the corresponding | |
413 | values retrieved from the GeoJSON feature data. | |
414 | ||
415 | Pagination | |
416 | ---------- | |
417 | ||
418 | We provide a ``GeoJsonPagination`` class. | |
419 | ||
420 | GeoJsonPagination | |
421 | ~~~~~~~~~~~~~~~~~ | |
422 | ||
423 | Based on ``rest_framework.pagination.PageNumberPagination``. | |
424 | ||
425 | Code example: | |
426 | ||
427 | .. code-block:: python | |
428 | ||
429 | from rest_framework_gis.pagination import GeoJsonPagination | |
430 | # --- other omitted imports --- # | |
431 | ||
432 | class GeojsonLocationList(generics.ListCreateAPIView): | |
433 | # -- other omitted view attributes --- # | |
434 | pagination_class = GeoJsonPagination | |
435 | ||
436 | Example result response (cut to one element only instead of 10): | |
437 | ||
438 | .. code-block:: javascript | |
439 | ||
440 | { | |
441 | "type": "FeatureCollection", | |
442 | "count": 25, | |
443 | "next": "http://localhost:8000/geojson/?page=2", | |
444 | "previous": null, | |
445 | "features": [ | |
446 | { | |
447 | "type": "Feature", | |
448 | "geometry": { | |
449 | "type": "Point", | |
450 | "coordinates": [ | |
451 | 42.0, | |
452 | 50.0 | |
453 | ] | |
454 | }, | |
455 | "properties": { | |
456 | "name": "test" | |
457 | } | |
458 | } | |
459 | ] | |
460 | } | |
461 | ||
462 | ||
463 | Filters | |
464 | ------- | |
465 | ||
466 | **note**: this feature has been tested up to django-filter 1.0. | |
467 | ||
468 | We provide a ``GeometryFilter`` field as well as a ``GeoFilterSet`` | |
469 | for usage with ``django_filter``. You simply provide, in the query | |
470 | string, one of the textual types supported by ``GEOSGeometry``. By | |
471 | default, this includes WKT, HEXEWKB, WKB (in a buffer), and GeoJSON. | |
472 | ||
473 | GeometryFilter | |
474 | ~~~~~~~~~~~~~~ | |
475 | ||
476 | .. code-block:: python | |
477 | ||
478 | from rest_framework_gis.filterset import GeoFilterSet | |
479 | from rest_framework_gis.filters import GeometryFilter | |
480 | from django_filters import filters | |
481 | ||
482 | class RegionFilter(GeoFilterSet): | |
483 | slug = filters.CharFilter(name='slug', lookup_expr='istartswith') | |
484 | contains_geom = GeometryFilter(name='geom', lookup_expr='contains') | |
485 | ||
486 | class Meta: | |
487 | model = Region | |
488 | ||
489 | We can then filter in the URL, using GeoJSON, and we will perform a | |
490 | ``__contains`` geometry lookup, e.g. | |
491 | ``/region/?contains_geom={ "type": "Point", "coordinates": [ -123.26436996459961, 44.564178042345375 ] }``. | |
492 | ||
493 | GeoFilterSet | |
494 | ~~~~~~~~~~~~ | |
495 | ||
496 | The ``GeoFilterSet`` provides a ``django_filter`` compatible | |
497 | ``FilterSet`` that will automatically create ``GeometryFilters`` for | |
498 | ``GeometryFields``. | |
499 | ||
500 | InBBoxFilter | |
501 | ~~~~~~~~~~~~ | |
502 | ||
503 | Provides a ``InBBoxFilter``, which is a subclass of DRF | |
504 | ``BaseFilterBackend``. Filters a queryset to only those instances within | |
505 | a certain bounding box. | |
506 | ||
507 | ||
508 | ``views.py:`` | |
509 | ||
510 | .. code-block:: python | |
511 | ||
512 | from rest_framework_gis.filters import InBBoxFilter | |
513 | ||
514 | class LocationList(ListAPIView): | |
515 | ||
516 | queryset = models.Location.objects.all() | |
517 | serializer_class = serializers.LocationSerializer | |
518 | bbox_filter_field = 'point' | |
519 | filter_backends = (InBBoxFilter, ) | |
520 | bbox_filter_include_overlapping = True # Optional | |
521 | ||
522 | We can then filter in the URL, using Bounding Box format (min Lon, min | |
523 | Lat, max Lon, max Lat), and we can search for instances within the | |
524 | bounding box, e.g.: | |
525 | ``/location/?in_bbox=-90,29,-89,35``. | |
526 | ||
527 | By default, InBBoxFilter will only return those instances entirely | |
528 | within the stated bounding box. To include those instances which overlap | |
529 | the bounding box, include ``bbox_filter_include_overlapping = True`` | |
530 | in your view. | |
531 | ||
532 | Note that if you are using other filters, you'll want to include your | |
533 | other filter backend in your view. For example: | |
534 | ||
535 | ``filter_backends = (InBBoxFilter, DjangoFilterBackend,)`` | |
536 | ||
537 | TMSTileFilter | |
538 | ~~~~~~~~~~~~~ | |
539 | ||
540 | Provides a ``TMSTileFilter``, which is a subclass of ``InBBoxFilter``. | |
541 | Filters a queryset to only those instances within a bounding box defined | |
542 | by a `TMS tile <http://wiki.openstreetmap.org/wiki/TMS>`__ address. | |
543 | ||
544 | ``views.py:`` | |
545 | ||
546 | .. code-block:: python | |
547 | ||
548 | from rest_framework_gis.filters import TMSTileFilter | |
549 | ||
550 | class LocationList(ListAPIView): | |
551 | ||
552 | queryset = models.Location.objects.all() | |
553 | serializer_class = serializers.LocationSerializer | |
554 | bbox_filter_field = 'point' | |
555 | filter_backends = (TMSTileFilter, ) | |
556 | bbox_filter_include_overlapping = True # Optional | |
557 | ||
558 | We can then filter in the URL, using TMS tile addresses in the zoom/x/y format, | |
559 | eg:. | |
560 | ``/location/?tile=8/100/200`` | |
561 | which is equivalant to filtering on the bbox (-39.37500,-71.07406,-37.96875,-70.61261). | |
562 | ||
563 | For more information on configuration options see InBBoxFilter. | |
564 | ||
565 | Note that the tile address start in the upper left, not the lower left origin used by some | |
566 | implementations. | |
567 | ||
568 | DistanceToPointFilter | |
569 | ~~~~~~~~~~~~~~~~~~~~~ | |
570 | ||
571 | Provides a ``DistanceToPointFilter``, which is a subclass of DRF | |
572 | ``BaseFilterBackend``. Filters a queryset to only those instances within | |
573 | a certain distance of a given point. | |
574 | ||
575 | ``views.py:`` | |
576 | ||
577 | .. code-block:: python | |
578 | ||
579 | from rest_framework_gis.filters import DistanceToPointFilter | |
580 | ||
581 | class LocationList(ListAPIView): | |
582 | ||
583 | queryset = models.Location.objects.all() | |
584 | serializer_class = serializers.LocationSerializer | |
585 | distance_filter_field = 'geometry' | |
586 | filter_backends = (DistanceToPointFilter, ) | |
587 | bbox_filter_include_overlapping = True # Optional | |
588 | ||
589 | We can then filter in the URL, using a distance and a point in (lon, lat) format. The | |
590 | distance can be given in meters or in degrees. | |
591 | ||
592 | eg:. | |
593 | ``/location/?dist=4000&point=-122.4862,37.7694&format=json`` | |
594 | which is equivalant to filtering within 4000 meters of the point (-122.4862, 37.7694). | |
595 | ||
596 | By default, DistanceToPointFilter will pass the 'distance' in the URL directly to the database for the search. | |
597 | The effect depends on the srid of the database in use. If geo data is indexed in meters (srid 3875, aka 900913), a | |
598 | distance in meters can be passed in directly without conversion. For lat-lon databases such as srid 4326, | |
599 | which is indexed in degrees, the 'distance' will be interpreted as degrees. Set the flag, 'distance_filter_convert_meters' | |
600 | to 'True' in order to convert an input distance in meters to degrees. This conversion is approximate, and the errors | |
601 | at latitudes > 60 degrees are > 25%. | |
602 | ||
603 | Projects using this package | |
604 | --------------------------- | |
605 | ||
606 | - `Nodeshot <https://github.com/ninuxorg/nodeshot>`__: Extensible Django web application for management of community-led georeferenced data | |
607 | ||
608 | Running the tests | |
609 | ----------------- | |
610 | ||
611 | Required setup | |
612 | ============== | |
613 | ||
614 | You need one of the `Spatial Database servers supported by | |
615 | GeoDjango <https://docs.djangoproject.com/en/dev/ref/contrib/gis/db-api/#module-django.contrib.gis.db.backends>`__, | |
616 | and create a database for the tests. | |
617 | ||
618 | The following can be used with PostgreSQL: | |
619 | ||
620 | .. code-block:: bash | |
621 | ||
622 | createdb django_restframework_gis | |
623 | psql -U postgres -d django_restframework_gis -c "CREATE EXTENSION postgis" | |
624 | ||
625 | You might need to tweak the DB settings according to your DB | |
626 | configuration. You can copy the file ``local_settings.example.py`` to | |
627 | ``local_settings.py`` and change the ``DATABASES`` and/or | |
628 | ``INSTALLED_APPS`` directives there. | |
629 | ||
630 | This should allow you to run the tests already. | |
631 | ||
632 | For reference, the following steps will setup a development environment for | |
633 | contributing to the project: | |
634 | ||
635 | - create a spatial database named "django\_restframework\_gis" | |
636 | - create ``local_settings.py``, eg: | |
637 | ``cp local_settings.example.py local_settings.py`` | |
638 | - tweak the ``DATABASES`` configuration directive according to your DB | |
639 | settings | |
640 | - uncomment ``INSTALLED_APPS`` | |
641 | - run ``python manage.py syncdb`` | |
642 | - run ``python manage.py collectstatic`` | |
643 | - run ``python manage.py runserver`` | |
644 | ||
645 | Using tox | |
646 | ========= | |
647 | ||
648 | The recommended way to run the tests is by using | |
649 | `tox <https://tox.readthedocs.io/en/latest/>`__, which can be installed using | |
650 | `pip install tox`. | |
651 | ||
652 | You can use ``tox -l`` to list the available environments, and then e.g. use | |
653 | the following to run all tests with Python 3.6 and Django 1.11: | |
654 | ||
655 | .. code-block:: bash | |
656 | ||
657 | tox -e py36-django111 | |
658 | ||
659 | By default Django's test runner is used, but there is a variation of tox's | |
660 | envlist to use pytest (using the ``-pytest`` suffix). | |
661 | ||
662 | You can pass optional arguments to the test runner like this: | |
663 | ||
664 | .. code-block:: bash | |
665 | ||
666 | tox -e py36-django111-pytest -- -k test_foo | |
667 | ||
668 | Running tests manually | |
669 | ====================== | |
670 | ||
671 | Please refer to the ``tox.ini`` file for reference/help in case you want to run | |
672 | tests manually / without tox. | |
673 | ||
674 | To run tests in docker use | |
675 | ||
676 | .. code-block:: bash | |
677 | docker-compose build | |
678 | docker-compose run --rm test | |
679 | ||
680 | ||
681 | Contributing | |
682 | ------------ | |
683 | ||
684 | 1. Join the `Django REST Framework GIS Mailing | |
685 | List <https://groups.google.com/forum/#!forum/django-rest-framework-gis>`__ | |
686 | and announce your intentions | |
687 | 2. Follow the `PEP8 Style Guide for Python | |
688 | Code <http://www.python.org/dev/peps/pep-0008/>`__ | |
689 | 3. Fork this repo | |
690 | 4. Write code | |
691 | 5. Write tests for your code | |
692 | 6. Ensure all tests pass | |
693 | 7. Ensure test coverage is not under 90% | |
694 | 8. Document your changes | |
695 | 9. Send pull request | |
696 | ||
697 | .. |Build Status| image:: https://travis-ci.org/djangonauts/django-rest-framework-gis.svg?branch=master | |
698 | :target: https://travis-ci.org/djangonauts/django-rest-framework-gis | |
699 | .. |Coverage Status| image:: https://coveralls.io/repos/djangonauts/django-rest-framework-gis/badge.svg | |
700 | :target: https://coveralls.io/r/djangonauts/django-rest-framework-gis | |
701 | .. |Requirements Status| image:: https://requires.io/github/djangonauts/django-rest-framework-gis/requirements.svg?branch=master | |
702 | :target: https://requires.io/github/djangonauts/django-rest-framework-gis/requirements/?branch=master | |
703 | .. |PyPI version| image:: https://badge.fury.io/py/djangorestframework-gis.svg | |
704 | :target: http://badge.fury.io/py/djangorestframework-gis | |
705 | ||
10 | 706 | Keywords: django,rest-framework,gis,geojson |
11 | 707 | Platform: Platform Indipendent |
12 | 708 | Classifier: Development Status :: 4 - Beta |
17 | 713 | Classifier: Operating System :: OS Independent |
18 | 714 | Classifier: Framework :: Django |
19 | 715 | Classifier: Programming Language :: Python |
20 | Classifier: Programming Language :: Python :: 2.7 | |
21 | 716 | Classifier: Programming Language :: Python :: 3 |
22 | Classifier: Programming Language :: Python :: 3.3 | |
23 | 717 | Classifier: Programming Language :: Python :: 3.4 |
24 | 718 | Classifier: Programming Language :: Python :: 3.5 |
719 | Classifier: Programming Language :: Python :: 3.6 | |
720 | Classifier: Programming Language :: Python :: 3.7 | |
721 | Classifier: Programming Language :: Python :: 3 :: Only |
38 | 38 | |
39 | 39 | =============== ============================ ==================== ================================== |
40 | 40 | DRF-gis version DRF version Django version Python version |
41 | **0.14.x** **3.3** to **3.9** **1.11** to **2.1** **3.4** to **3.7** | |
41 | 42 | **0.13.x** **3.3** to **3.8** **1.11** to **2.0** **2.7** to **3.6** |
42 | 43 | **0.12.x** **3.1** to **3.7** **1.11** to **2.0** **2.7** to **3.6** |
43 | 44 | **0.11.x** **3.1** to **3.6** **1.7** to **1.11** **2.7** to **3.6** |
656 | 657 | Please refer to the ``tox.ini`` file for reference/help in case you want to run |
657 | 658 | tests manually / without tox. |
658 | 659 | |
660 | To run tests in docker use | |
661 | ||
662 | .. code-block:: bash | |
663 | docker-compose build | |
664 | docker-compose run --rm test | |
665 | ||
666 | ||
659 | 667 | Contributing |
660 | 668 | ------------ |
661 | 669 |
0 | 0 | Metadata-Version: 1.1 |
1 | 1 | Name: djangorestframework-gis |
2 | Version: 0.13 | |
2 | Version: 0.14 | |
3 | 3 | Summary: Geographic add-ons for Django Rest Framework |
4 | 4 | Home-page: https://github.com/djangonauts/django-rest-framework-gis |
5 | 5 | Author: Douglas Meehan |
6 | 6 | Author-email: django-rest-framework-gis@googlegroups.com |
7 | 7 | License: BSD |
8 | 8 | Download-URL: https://github.com/djangonauts/django-rest-framework-gis/releases |
9 | Description: UNKNOWN | |
9 | Project-URL: Bug Reports, https://github.com/djangonauts/django-rest-framework-gis/issues | |
10 | Project-URL: Source Code, https://github.com/djangonauts/django-rest-framework-gis | |
11 | Project-URL: Code Coverage, https://coveralls.io/github/djangonauts/django-rest-framework-gis | |
12 | Project-URL: Mailing List, https://groups.google.com/forum/#!forum/django-rest-framework-gis | |
13 | Project-URL: Continuous Integration, https://travis-ci.org/djangonauts/django-rest-framework-gis | |
14 | Description: django-rest-framework-gis | |
15 | ========================= | |
16 | ||
17 | |Build Status| |Coverage Status| |Requirements Status| |PyPI version| | |
18 | ||
19 | Geographic add-ons for Django Rest Framework - `Mailing | |
20 | List <http://bit.ly/1M4sLTp>`__. | |
21 | ||
22 | Install last stable version from pypi | |
23 | ------------------------------------- | |
24 | ||
25 | .. code-block:: bash | |
26 | ||
27 | pip install djangorestframework-gis | |
28 | ||
29 | Install development version | |
30 | --------------------------- | |
31 | ||
32 | .. code-block:: bash | |
33 | ||
34 | pip install https://github.com/djangonauts/django-rest-framework-gis/tarball/master | |
35 | ||
36 | Setup | |
37 | ----- | |
38 | ||
39 | Add ``rest_framework_gis`` in ``settings.INSTALLED_APPS``, after ``rest_framework``: | |
40 | ||
41 | .. code-block:: python | |
42 | ||
43 | INSTALLED_APPS = [ | |
44 | # ... | |
45 | 'rest_framework', | |
46 | 'rest_framework_gis', | |
47 | # ... | |
48 | ] | |
49 | ||
50 | Compatibility with DRF, Django and Python | |
51 | ----------------------------------------- | |
52 | ||
53 | =============== ============================ ==================== ================================== | |
54 | DRF-gis version DRF version Django version Python version | |
55 | **0.14.x** **3.3** to **3.9** **1.11** to **2.1** **3.4** to **3.7** | |
56 | **0.13.x** **3.3** to **3.8** **1.11** to **2.0** **2.7** to **3.6** | |
57 | **0.12.x** **3.1** to **3.7** **1.11** to **2.0** **2.7** to **3.6** | |
58 | **0.11.x** **3.1** to **3.6** **1.7** to **1.11** **2.7** to **3.6** | |
59 | **0.10.x** **3.1** to **3.3** **1.7** to **1.9** **2.7** to **3.5** | |
60 | **0.9.6** **3.1** to **3.2** **1.5** to **1.8** **2.6** to **3.5** | |
61 | **0.9.5** **3.1** to **3.2** **1.5** to **1.8** **2.6** to **3.4** | |
62 | **0.9.4** **3.1** to **3.2** **1.5** to **1.8** **2.6** to **3.4** | |
63 | **0.9.3** **3.1** **1.5** to **1.8** **2.6** to **3.4** | |
64 | **0.9.2** **3.1** **1.5** to **1.8** **2.6** to **3.4** | |
65 | **0.9.1** **3.1** **1.5** to **1.8** **2.6** to **3.4** | |
66 | **0.9** **3.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** | |
67 | **0.9** **3.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** | |
68 | **0.9** **3.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** | |
69 | **0.8.2** **3.0.4** to **3.1.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** | |
70 | **0.8.1** **3.0.4** to **3.1.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** | |
71 | **0.8** **3.0.4** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** | |
72 | **0.7** **2.4.3** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** | |
73 | **0.6** **2.4.3** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** | |
74 | **0.5** from **2.3.14** to **2.4.2** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** | |
75 | **0.4** from **2.3.14** to **2.4.2** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** | |
76 | **0.3** from **2.3.14** to **2.4.2** **1.5**, **1.6** **2.6**, **2.7** | |
77 | **0.2** from **2.2.2** to **2.3.13** **1.5**, **1.6** **2.6**, **2.7** | |
78 | =============== ============================ ==================== ================================== | |
79 | ||
80 | Fields | |
81 | ------ | |
82 | ||
83 | GeometryField | |
84 | ~~~~~~~~~~~~~ | |
85 | ||
86 | Provides a ``GeometryField``, which is a subclass of Django Rest Framework | |
87 | (from now on **DRF**) ``WritableField``. This field handles GeoDjango | |
88 | geometry fields, providing custom ``to_native`` and ``from_native`` | |
89 | methods for GeoJSON input/output. | |
90 | ||
91 | This field takes two optional arguments: | |
92 | ||
93 | ``precision``: Passes coordinates through Python's builtin ``round()`` function (`docs | |
94 | <https://docs.python.org/3/library/functions.html#round>`_), rounding values to | |
95 | the provided level of precision. E.g. A Point with lat/lng of | |
96 | ``[51.0486, -114.0708]`` passed through a ``GeometryField(precision=2)`` | |
97 | would return a Point with a lat/lng of ``[51.05, -114.07]``. | |
98 | ||
99 | ``remove_duplicates``: Remove sequential duplicate coordinates from line and | |
100 | polygon geometries. This is particularly useful when used with the ``precision`` | |
101 | argument, as the likelihood of duplicate coordinates increase as precision of | |
102 | coordinates are reduced. | |
103 | ||
104 | **Note:** While both above arguments are designed to reduce the | |
105 | byte size of the API response, they will also increase the processing time | |
106 | required to render the response. This will likely be negligible for small GeoJSON | |
107 | responses but may become an issue for large responses. | |
108 | ||
109 | **New in 0.9.3:** there is no need to define this field explicitly in your serializer, | |
110 | it's mapped automatically during initialization in ``rest_framework_gis.apps.AppConfig.ready()``. | |
111 | ||
112 | GeometrySerializerMethodField | |
113 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
114 | ||
115 | Provides a ``GeometrySerializerMethodField``, which is a subclass of DRF | |
116 | ``SerializerMethodField`` and handles values which are computed with a serializer | |
117 | method and are used as a ``geo_field``. `See example below <https://github.com/djangonauts/django-rest-framework-gis#using-geometryserializermethodfield-as-geo_field>`__. | |
118 | ||
119 | Serializers | |
120 | ----------- | |
121 | ||
122 | GeoModelSerializer (DEPRECATED) | |
123 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
124 | ||
125 | **Deprecated, will be removed in 1.0**: Using this serializer is not needed anymore since 0.9.3 if you add | |
126 | ``rest_framework_gis`` in ``settings.INSTALLED_APPS`` | |
127 | ||
128 | Provides a ``GeoModelSerializer``, which is a subclass of DRF | |
129 | ``ModelSerializer``. This serializer updates the field\_mapping | |
130 | dictionary to include field mapping of GeoDjango geometry fields to the | |
131 | above ``GeometryField``. | |
132 | ||
133 | For example, the following model: | |
134 | ||
135 | .. code-block:: python | |
136 | ||
137 | class Location(models.Model): | |
138 | """ | |
139 | A model which holds information about a particular location | |
140 | """ | |
141 | address = models.Charfield(max_length=255) | |
142 | city = models.CharField(max_length=100) | |
143 | state = models.CharField(max_length=100) | |
144 | point = models.PointField() | |
145 | ||
146 | By default, the DRF ModelSerializer will output: | |
147 | ||
148 | .. code-block:: javascript | |
149 | ||
150 | { | |
151 | "id": 1, | |
152 | "address": "742 Evergreen Terrace", | |
153 | "city": "Springfield", | |
154 | "state": "Oregon", | |
155 | "point": "POINT(-123.0208 44.0464)" | |
156 | } | |
157 | ||
158 | In contrast, the ``GeoModelSerializer`` will output: | |
159 | ||
160 | .. code-block:: javascript | |
161 | ||
162 | { | |
163 | "id": 1, | |
164 | "address": "742 Evergreen Terrace", | |
165 | "city": "Springfield", | |
166 | "state": "Oregon", | |
167 | "point": { | |
168 | "type": "Point", | |
169 | "coordinates": [-123.0208, 44.0464], | |
170 | } | |
171 | } | |
172 | ||
173 | GeoFeatureModelSerializer | |
174 | ~~~~~~~~~~~~~~~~~~~~~~~~~ | |
175 | ||
176 | ``GeoFeatureModelSerializer`` is a subclass of ``rest_framework.ModelSerializer`` | |
177 | which will output data in a format that is **GeoJSON** compatible. Using | |
178 | the above example, the ``GeoFeatureModelSerializer`` will output: | |
179 | ||
180 | .. code-block:: javascript | |
181 | ||
182 | { | |
183 | "id": 1, | |
184 | "type": "Feature", | |
185 | "geometry": { | |
186 | "point": { | |
187 | "type": "Point", | |
188 | "coordinates": [-123.0208, 44.0464], | |
189 | }, | |
190 | }, | |
191 | "properties": { | |
192 | "address": "742 Evergreen Terrace", | |
193 | "city": "Springfield", | |
194 | "state": "Oregon" | |
195 | } | |
196 | } | |
197 | ||
198 | If you are serializing an object list, ``GeoFeatureModelSerializer`` | |
199 | will create a ``FeatureCollection``: | |
200 | ||
201 | .. code-block:: javascript | |
202 | ||
203 | { | |
204 | "type": "FeatureCollection", | |
205 | "features": [ | |
206 | { | |
207 | "id": 1 | |
208 | "type": "Feature", | |
209 | "geometry": { | |
210 | "point": { | |
211 | "type": "Point", | |
212 | "coordinates": [-123.0208, 44.0464], | |
213 | } | |
214 | }, | |
215 | "properties": { | |
216 | "address": "742 Evergreen Terrace", | |
217 | "city": "Springfield", | |
218 | "state": "Oregon", | |
219 | } | |
220 | } | |
221 | { | |
222 | "id": 2, | |
223 | "type": "Feature", | |
224 | "geometry": { | |
225 | "point": { | |
226 | "type": "Point", | |
227 | "coordinates": [-123.0208, 44.0489], | |
228 | }, | |
229 | }, | |
230 | "properties": { | |
231 | "address": "744 Evergreen Terrace", | |
232 | "city": "Springfield", | |
233 | "state": "Oregon" | |
234 | } | |
235 | } | |
236 | } | |
237 | ||
238 | Specifying the geometry field: "geo_field" | |
239 | ########################################## | |
240 | ||
241 | ``GeoFeatureModelSerializer`` requires you to define a ``geo_field`` | |
242 | to be serialized as the "geometry". For example: | |
243 | ||
244 | .. code-block:: python | |
245 | ||
246 | from rest_framework_gis.serializers import GeoFeatureModelSerializer | |
247 | ||
248 | class LocationSerializer(GeoFeatureModelSerializer): | |
249 | """ A class to serialize locations as GeoJSON compatible data """ | |
250 | ||
251 | class Meta: | |
252 | model = Location | |
253 | geo_field = "point" | |
254 | ||
255 | # you can also explicitly declare which fields you want to include | |
256 | # as with a ModelSerializer. | |
257 | fields = ('id', 'address', 'city', 'state') | |
258 | ||
259 | Using GeometrySerializerMethodField as "geo_field" | |
260 | ################################################## | |
261 | ||
262 | ``geo_field`` may also be an instance of ``GeometrySerializerMethodField``. | |
263 | In this case you can compute its value during serialization. For example: | |
264 | ||
265 | .. code-block:: python | |
266 | ||
267 | from django.contrib.gis.geos import Point | |
268 | from rest_framework_gis.serializers import GeoFeatureModelSerializer, GeometrySerializerMethodField | |
269 | ||
270 | class LocationSerializer(GeoFeatureModelSerializer): | |
271 | """ A class to serialize locations as GeoJSON compatible data """ | |
272 | ||
273 | # a field which contains a geometry value and can be used as geo_field | |
274 | other_point = GeometrySerializerMethodField() | |
275 | ||
276 | def get_other_point(self, obj): | |
277 | return Point(obj.point.lat / 2, obj.point.lon / 2) | |
278 | ||
279 | class Meta: | |
280 | model = Location | |
281 | geo_field = 'other_point' | |
282 | ||
283 | Serializer for ``geo_field`` may also return ``None`` value, which will translate to ``null`` value for geojson ``geometry`` field. | |
284 | ||
285 | Specifying the ID: "id_field" | |
286 | ############################# | |
287 | ||
288 | The primary key of the model (usually the "id" attribute) is | |
289 | automatically used as the ``id`` field of each | |
290 | `GeoJSON Feature Object <https://tools.ietf.org/html/draft-butler-geojson#section-2.2>`_. | |
291 | ||
292 | The default behaviour follows the `GeoJSON RFC <https://tools.ietf.org/html/draft-butler-geojson>`_, | |
293 | but it can be disbaled by setting ``id_field`` to ``False``: | |
294 | ||
295 | .. code-block:: python | |
296 | ||
297 | from rest_framework_gis.serializers import GeoFeatureModelSerializer | |
298 | ||
299 | class LocationSerializer(GeoFeatureModelSerializer): | |
300 | ||
301 | class Meta: | |
302 | model = Location | |
303 | geo_field = "point" | |
304 | id_field = False | |
305 | fields = ('id', 'address', 'city', 'state') | |
306 | ||
307 | The ``id_field`` can also be set to use some other unique field in your model, eg: ``slug``: | |
308 | ||
309 | .. code-block:: python | |
310 | ||
311 | from rest_framework_gis.serializers import GeoFeatureModelSerializer | |
312 | ||
313 | class LocationSerializer(GeoFeatureModelSerializer): | |
314 | ||
315 | class Meta: | |
316 | model = Location | |
317 | geo_field = 'point' | |
318 | id_field = 'slug' | |
319 | fields = ('slug', 'address', 'city', 'state') | |
320 | ||
321 | Bounding Box: "auto_bbox" and "bbox_geo_field" | |
322 | ############################################## | |
323 | ||
324 | The GeoJSON specification allows a feature to contain a | |
325 | `boundingbox of a feature <http://geojson.org/geojson-spec.html#geojson-objects>`__. | |
326 | ``GeoFeatureModelSerializer`` allows two different ways to fill this property. The first | |
327 | is using the ``geo_field`` to calculate the bounding box of a feature. This only allows | |
328 | read access for a REST client and can be achieved using ``auto_bbox``. Example: | |
329 | ||
330 | .. code-block:: python | |
331 | ||
332 | from rest_framework_gis.serializers import GeoFeatureModelSerializer | |
333 | ||
334 | class LocationSerializer(GeoFeatureModelSerializer): | |
335 | class Meta: | |
336 | model = Location | |
337 | geo_field = 'geometry' | |
338 | auto_bbox = True | |
339 | ||
340 | ||
341 | The second approach uses the ``bbox_geo_field`` to specify an additional | |
342 | ``GeometryField`` of the model which will be used to calculate the bounding box. This allows | |
343 | boundingboxes differ from the exact extent of a features geometry. Additionally this | |
344 | enables read and write access for the REST client. Bounding boxes send from the client will | |
345 | be saved as Polygons. Example: | |
346 | ||
347 | .. code-block:: python | |
348 | ||
349 | from rest_framework_gis.serializers import GeoFeatureModelSerializer | |
350 | ||
351 | class LocationSerializer(GeoFeatureModelSerializer): | |
352 | ||
353 | class Meta: | |
354 | model = BoxedLocation | |
355 | geo_field = 'geometry' | |
356 | bbox_geo_field = 'bbox_geometry' | |
357 | ||
358 | ||
359 | Custom GeoJSON properties source | |
360 | ################################ | |
361 | ||
362 | In GeoJSON each feature can have a ``properties`` member containing the | |
363 | attributes of the feature. By default this field is filled with the | |
364 | attributes from your Django model, excluding the id, geometry and bounding | |
365 | box fields. It's possible to override this behaviour and implement a custom | |
366 | source for the ``properties`` member. | |
367 | ||
368 | The following example shows how to use a PostgreSQL HStore field as a source for | |
369 | the ``properties`` member: | |
370 | ||
371 | .. code-block:: python | |
372 | ||
373 | # models.py | |
374 | class Link(models.Model): | |
375 | """ | |
376 | Metadata is stored in a PostgreSQL HStore field, which allows us to | |
377 | store arbitrary key-value pairs with a link record. | |
378 | """ | |
379 | metadata = HStoreField(blank=True, null=True, default=dict) | |
380 | geo = models.LineStringField() | |
381 | objects = models.GeoManager() | |
382 | ||
383 | # serializers.py | |
384 | class NetworkGeoSerializer(GeoFeatureModelSerializer): | |
385 | class Meta: | |
386 | model = models.Link | |
387 | geo_field = 'geo' | |
388 | auto_bbox = True | |
389 | ||
390 | def get_properties(self, instance, fields): | |
391 | # This is a PostgreSQL HStore field, which django maps to a dict | |
392 | return instance.metadata | |
393 | ||
394 | def unformat_geojson(self, feature): | |
395 | attrs = { | |
396 | self.Meta.geo_field: feature["geometry"], | |
397 | "metadata": feature["properties"] | |
398 | } | |
399 | ||
400 | if self.Meta.bbox_geo_field and "bbox" in feature: | |
401 | attrs[self.Meta.bbox_geo_field] = Polygon.from_bbox(feature["bbox"]) | |
402 | ||
403 | return attrs | |
404 | ||
405 | When the serializer renders GeoJSON, it calls the method | |
406 | ``get_properties`` for each object in the database. This function | |
407 | should return a dictionary containing the attributes for the feature. In the | |
408 | case of a HStore field, this function is easily implemented. | |
409 | ||
410 | The reverse is also required: mapping a GeoJSON formatted structure to | |
411 | attributes of your model. This task is done by ``unformat_geojson``. It should | |
412 | return a dictionary with your model attributes as keys, and the corresponding | |
413 | values retrieved from the GeoJSON feature data. | |
414 | ||
415 | Pagination | |
416 | ---------- | |
417 | ||
418 | We provide a ``GeoJsonPagination`` class. | |
419 | ||
420 | GeoJsonPagination | |
421 | ~~~~~~~~~~~~~~~~~ | |
422 | ||
423 | Based on ``rest_framework.pagination.PageNumberPagination``. | |
424 | ||
425 | Code example: | |
426 | ||
427 | .. code-block:: python | |
428 | ||
429 | from rest_framework_gis.pagination import GeoJsonPagination | |
430 | # --- other omitted imports --- # | |
431 | ||
432 | class GeojsonLocationList(generics.ListCreateAPIView): | |
433 | # -- other omitted view attributes --- # | |
434 | pagination_class = GeoJsonPagination | |
435 | ||
436 | Example result response (cut to one element only instead of 10): | |
437 | ||
438 | .. code-block:: javascript | |
439 | ||
440 | { | |
441 | "type": "FeatureCollection", | |
442 | "count": 25, | |
443 | "next": "http://localhost:8000/geojson/?page=2", | |
444 | "previous": null, | |
445 | "features": [ | |
446 | { | |
447 | "type": "Feature", | |
448 | "geometry": { | |
449 | "type": "Point", | |
450 | "coordinates": [ | |
451 | 42.0, | |
452 | 50.0 | |
453 | ] | |
454 | }, | |
455 | "properties": { | |
456 | "name": "test" | |
457 | } | |
458 | } | |
459 | ] | |
460 | } | |
461 | ||
462 | ||
463 | Filters | |
464 | ------- | |
465 | ||
466 | **note**: this feature has been tested up to django-filter 1.0. | |
467 | ||
468 | We provide a ``GeometryFilter`` field as well as a ``GeoFilterSet`` | |
469 | for usage with ``django_filter``. You simply provide, in the query | |
470 | string, one of the textual types supported by ``GEOSGeometry``. By | |
471 | default, this includes WKT, HEXEWKB, WKB (in a buffer), and GeoJSON. | |
472 | ||
473 | GeometryFilter | |
474 | ~~~~~~~~~~~~~~ | |
475 | ||
476 | .. code-block:: python | |
477 | ||
478 | from rest_framework_gis.filterset import GeoFilterSet | |
479 | from rest_framework_gis.filters import GeometryFilter | |
480 | from django_filters import filters | |
481 | ||
482 | class RegionFilter(GeoFilterSet): | |
483 | slug = filters.CharFilter(name='slug', lookup_expr='istartswith') | |
484 | contains_geom = GeometryFilter(name='geom', lookup_expr='contains') | |
485 | ||
486 | class Meta: | |
487 | model = Region | |
488 | ||
489 | We can then filter in the URL, using GeoJSON, and we will perform a | |
490 | ``__contains`` geometry lookup, e.g. | |
491 | ``/region/?contains_geom={ "type": "Point", "coordinates": [ -123.26436996459961, 44.564178042345375 ] }``. | |
492 | ||
493 | GeoFilterSet | |
494 | ~~~~~~~~~~~~ | |
495 | ||
496 | The ``GeoFilterSet`` provides a ``django_filter`` compatible | |
497 | ``FilterSet`` that will automatically create ``GeometryFilters`` for | |
498 | ``GeometryFields``. | |
499 | ||
500 | InBBoxFilter | |
501 | ~~~~~~~~~~~~ | |
502 | ||
503 | Provides a ``InBBoxFilter``, which is a subclass of DRF | |
504 | ``BaseFilterBackend``. Filters a queryset to only those instances within | |
505 | a certain bounding box. | |
506 | ||
507 | ||
508 | ``views.py:`` | |
509 | ||
510 | .. code-block:: python | |
511 | ||
512 | from rest_framework_gis.filters import InBBoxFilter | |
513 | ||
514 | class LocationList(ListAPIView): | |
515 | ||
516 | queryset = models.Location.objects.all() | |
517 | serializer_class = serializers.LocationSerializer | |
518 | bbox_filter_field = 'point' | |
519 | filter_backends = (InBBoxFilter, ) | |
520 | bbox_filter_include_overlapping = True # Optional | |
521 | ||
522 | We can then filter in the URL, using Bounding Box format (min Lon, min | |
523 | Lat, max Lon, max Lat), and we can search for instances within the | |
524 | bounding box, e.g.: | |
525 | ``/location/?in_bbox=-90,29,-89,35``. | |
526 | ||
527 | By default, InBBoxFilter will only return those instances entirely | |
528 | within the stated bounding box. To include those instances which overlap | |
529 | the bounding box, include ``bbox_filter_include_overlapping = True`` | |
530 | in your view. | |
531 | ||
532 | Note that if you are using other filters, you'll want to include your | |
533 | other filter backend in your view. For example: | |
534 | ||
535 | ``filter_backends = (InBBoxFilter, DjangoFilterBackend,)`` | |
536 | ||
537 | TMSTileFilter | |
538 | ~~~~~~~~~~~~~ | |
539 | ||
540 | Provides a ``TMSTileFilter``, which is a subclass of ``InBBoxFilter``. | |
541 | Filters a queryset to only those instances within a bounding box defined | |
542 | by a `TMS tile <http://wiki.openstreetmap.org/wiki/TMS>`__ address. | |
543 | ||
544 | ``views.py:`` | |
545 | ||
546 | .. code-block:: python | |
547 | ||
548 | from rest_framework_gis.filters import TMSTileFilter | |
549 | ||
550 | class LocationList(ListAPIView): | |
551 | ||
552 | queryset = models.Location.objects.all() | |
553 | serializer_class = serializers.LocationSerializer | |
554 | bbox_filter_field = 'point' | |
555 | filter_backends = (TMSTileFilter, ) | |
556 | bbox_filter_include_overlapping = True # Optional | |
557 | ||
558 | We can then filter in the URL, using TMS tile addresses in the zoom/x/y format, | |
559 | eg:. | |
560 | ``/location/?tile=8/100/200`` | |
561 | which is equivalant to filtering on the bbox (-39.37500,-71.07406,-37.96875,-70.61261). | |
562 | ||
563 | For more information on configuration options see InBBoxFilter. | |
564 | ||
565 | Note that the tile address start in the upper left, not the lower left origin used by some | |
566 | implementations. | |
567 | ||
568 | DistanceToPointFilter | |
569 | ~~~~~~~~~~~~~~~~~~~~~ | |
570 | ||
571 | Provides a ``DistanceToPointFilter``, which is a subclass of DRF | |
572 | ``BaseFilterBackend``. Filters a queryset to only those instances within | |
573 | a certain distance of a given point. | |
574 | ||
575 | ``views.py:`` | |
576 | ||
577 | .. code-block:: python | |
578 | ||
579 | from rest_framework_gis.filters import DistanceToPointFilter | |
580 | ||
581 | class LocationList(ListAPIView): | |
582 | ||
583 | queryset = models.Location.objects.all() | |
584 | serializer_class = serializers.LocationSerializer | |
585 | distance_filter_field = 'geometry' | |
586 | filter_backends = (DistanceToPointFilter, ) | |
587 | bbox_filter_include_overlapping = True # Optional | |
588 | ||
589 | We can then filter in the URL, using a distance and a point in (lon, lat) format. The | |
590 | distance can be given in meters or in degrees. | |
591 | ||
592 | eg:. | |
593 | ``/location/?dist=4000&point=-122.4862,37.7694&format=json`` | |
594 | which is equivalant to filtering within 4000 meters of the point (-122.4862, 37.7694). | |
595 | ||
596 | By default, DistanceToPointFilter will pass the 'distance' in the URL directly to the database for the search. | |
597 | The effect depends on the srid of the database in use. If geo data is indexed in meters (srid 3875, aka 900913), a | |
598 | distance in meters can be passed in directly without conversion. For lat-lon databases such as srid 4326, | |
599 | which is indexed in degrees, the 'distance' will be interpreted as degrees. Set the flag, 'distance_filter_convert_meters' | |
600 | to 'True' in order to convert an input distance in meters to degrees. This conversion is approximate, and the errors | |
601 | at latitudes > 60 degrees are > 25%. | |
602 | ||
603 | Projects using this package | |
604 | --------------------------- | |
605 | ||
606 | - `Nodeshot <https://github.com/ninuxorg/nodeshot>`__: Extensible Django web application for management of community-led georeferenced data | |
607 | ||
608 | Running the tests | |
609 | ----------------- | |
610 | ||
611 | Required setup | |
612 | ============== | |
613 | ||
614 | You need one of the `Spatial Database servers supported by | |
615 | GeoDjango <https://docs.djangoproject.com/en/dev/ref/contrib/gis/db-api/#module-django.contrib.gis.db.backends>`__, | |
616 | and create a database for the tests. | |
617 | ||
618 | The following can be used with PostgreSQL: | |
619 | ||
620 | .. code-block:: bash | |
621 | ||
622 | createdb django_restframework_gis | |
623 | psql -U postgres -d django_restframework_gis -c "CREATE EXTENSION postgis" | |
624 | ||
625 | You might need to tweak the DB settings according to your DB | |
626 | configuration. You can copy the file ``local_settings.example.py`` to | |
627 | ``local_settings.py`` and change the ``DATABASES`` and/or | |
628 | ``INSTALLED_APPS`` directives there. | |
629 | ||
630 | This should allow you to run the tests already. | |
631 | ||
632 | For reference, the following steps will setup a development environment for | |
633 | contributing to the project: | |
634 | ||
635 | - create a spatial database named "django\_restframework\_gis" | |
636 | - create ``local_settings.py``, eg: | |
637 | ``cp local_settings.example.py local_settings.py`` | |
638 | - tweak the ``DATABASES`` configuration directive according to your DB | |
639 | settings | |
640 | - uncomment ``INSTALLED_APPS`` | |
641 | - run ``python manage.py syncdb`` | |
642 | - run ``python manage.py collectstatic`` | |
643 | - run ``python manage.py runserver`` | |
644 | ||
645 | Using tox | |
646 | ========= | |
647 | ||
648 | The recommended way to run the tests is by using | |
649 | `tox <https://tox.readthedocs.io/en/latest/>`__, which can be installed using | |
650 | `pip install tox`. | |
651 | ||
652 | You can use ``tox -l`` to list the available environments, and then e.g. use | |
653 | the following to run all tests with Python 3.6 and Django 1.11: | |
654 | ||
655 | .. code-block:: bash | |
656 | ||
657 | tox -e py36-django111 | |
658 | ||
659 | By default Django's test runner is used, but there is a variation of tox's | |
660 | envlist to use pytest (using the ``-pytest`` suffix). | |
661 | ||
662 | You can pass optional arguments to the test runner like this: | |
663 | ||
664 | .. code-block:: bash | |
665 | ||
666 | tox -e py36-django111-pytest -- -k test_foo | |
667 | ||
668 | Running tests manually | |
669 | ====================== | |
670 | ||
671 | Please refer to the ``tox.ini`` file for reference/help in case you want to run | |
672 | tests manually / without tox. | |
673 | ||
674 | To run tests in docker use | |
675 | ||
676 | .. code-block:: bash | |
677 | docker-compose build | |
678 | docker-compose run --rm test | |
679 | ||
680 | ||
681 | Contributing | |
682 | ------------ | |
683 | ||
684 | 1. Join the `Django REST Framework GIS Mailing | |
685 | List <https://groups.google.com/forum/#!forum/django-rest-framework-gis>`__ | |
686 | and announce your intentions | |
687 | 2. Follow the `PEP8 Style Guide for Python | |
688 | Code <http://www.python.org/dev/peps/pep-0008/>`__ | |
689 | 3. Fork this repo | |
690 | 4. Write code | |
691 | 5. Write tests for your code | |
692 | 6. Ensure all tests pass | |
693 | 7. Ensure test coverage is not under 90% | |
694 | 8. Document your changes | |
695 | 9. Send pull request | |
696 | ||
697 | .. |Build Status| image:: https://travis-ci.org/djangonauts/django-rest-framework-gis.svg?branch=master | |
698 | :target: https://travis-ci.org/djangonauts/django-rest-framework-gis | |
699 | .. |Coverage Status| image:: https://coveralls.io/repos/djangonauts/django-rest-framework-gis/badge.svg | |
700 | :target: https://coveralls.io/r/djangonauts/django-rest-framework-gis | |
701 | .. |Requirements Status| image:: https://requires.io/github/djangonauts/django-rest-framework-gis/requirements.svg?branch=master | |
702 | :target: https://requires.io/github/djangonauts/django-rest-framework-gis/requirements/?branch=master | |
703 | .. |PyPI version| image:: https://badge.fury.io/py/djangorestframework-gis.svg | |
704 | :target: http://badge.fury.io/py/djangorestframework-gis | |
705 | ||
10 | 706 | Keywords: django,rest-framework,gis,geojson |
11 | 707 | Platform: Platform Indipendent |
12 | 708 | Classifier: Development Status :: 4 - Beta |
17 | 713 | Classifier: Operating System :: OS Independent |
18 | 714 | Classifier: Framework :: Django |
19 | 715 | Classifier: Programming Language :: Python |
20 | Classifier: Programming Language :: Python :: 2.7 | |
21 | 716 | Classifier: Programming Language :: Python :: 3 |
22 | Classifier: Programming Language :: Python :: 3.3 | |
23 | 717 | Classifier: Programming Language :: Python :: 3.4 |
24 | 718 | Classifier: Programming Language :: Python :: 3.5 |
719 | Classifier: Programming Language :: Python :: 3.6 | |
720 | Classifier: Programming Language :: Python :: 3.7 | |
721 | Classifier: Programming Language :: Python :: 3 :: Only |
3 | 3 | MANIFEST.in |
4 | 4 | README.rst |
5 | 5 | requirements.txt |
6 | runtests.py | |
7 | 6 | setup.cfg |
8 | 7 | setup.py |
9 | 8 | djangorestframework_gis.egg-info/PKG-INFO |
0 | VERSION = (0, 13, 0, 'final') | |
0 | VERSION = (0, 14, 0, 'final') | |
1 | 1 | __version__ = VERSION # alias |
2 | 2 | |
3 | 3 |
0 | import six # TODO Remove this along with GeoJsonDict when support for python 2.6/2.7 is dropped. | |
1 | 0 | import json |
2 | 1 | from collections import OrderedDict |
3 | 2 | |
4 | 3 | from django.contrib.gis.geos import GEOSGeometry, GEOSException |
5 | 4 | from django.contrib.gis.gdal import GDALException |
6 | 5 | from django.core.exceptions import ValidationError |
6 | from django.utils import six # TODO Remove this along with GeoJsonDict when support for python 2.6/2.7 is dropped. | |
7 | 7 | from django.utils.translation import ugettext_lazy as _ |
8 | 8 | from rest_framework.fields import Field, SerializerMethodField |
9 | 9 |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | import os | |
4 | import sys | |
5 | ||
6 | sys.path.insert(0, "tests") | |
7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") | |
8 | ||
9 | if __name__ == "__main__": | |
10 | from django.core.management import execute_from_command_line | |
11 | args = sys.argv | |
12 | args.insert(1, "test") | |
13 | args.insert(2, "django_restframework_gis_tests") | |
14 | execute_from_command_line(args) |
0 | 0 | #!/usr/bin/env python |
1 | import os | |
1 | 2 | import sys |
2 | import os | |
3 | from setuptools import setup, find_packages | |
3 | ||
4 | from setuptools import find_packages, setup | |
5 | ||
4 | 6 | from rest_framework_gis import get_version |
5 | ||
6 | ||
7 | def get_install_requires(): | |
8 | """ | |
9 | parse requirements.txt, ignore links, exclude comments | |
10 | """ | |
11 | requirements = [] | |
12 | for line in open('requirements.txt').readlines(): | |
13 | # skip to next iteration if comment or empty line | |
14 | if line.startswith('#') or line == '' or line.startswith('http') or line.startswith('git'): | |
15 | continue | |
16 | # add line to requirements | |
17 | requirements.append(line) | |
18 | return requirements | |
19 | ||
20 | 7 | |
21 | 8 | if sys.argv[-1] == 'publish': |
22 | 9 | os.system("python setup.py sdist bdist_wheel") |
28 | 15 | print(" git push --tags") |
29 | 16 | sys.exit() |
30 | 17 | |
18 | here = os.path.abspath(os.path.dirname(__file__)) | |
19 | ||
20 | # Get the long description from the README file | |
21 | with open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: | |
22 | long_description = f.read() | |
23 | ||
31 | 24 | |
32 | 25 | setup( |
33 | 26 | name='djangorestframework-gis', |
36 | 29 | author='Douglas Meehan', |
37 | 30 | author_email='django-rest-framework-gis@googlegroups.com', |
38 | 31 | description='Geographic add-ons for Django Rest Framework', |
32 | long_description=long_description, | |
39 | 33 | url='https://github.com/djangonauts/django-rest-framework-gis', |
40 | 34 | download_url='https://github.com/djangonauts/django-rest-framework-gis/releases', |
41 | 35 | platforms=['Platform Indipendent'], |
42 | 36 | keywords=['django', 'rest-framework', 'gis', 'geojson'], |
43 | 37 | packages=find_packages(exclude=['tests', 'tests.*']), |
44 | install_requires=get_install_requires(), | |
38 | install_requires=[ | |
39 | 'djangorestframework', | |
40 | ], | |
45 | 41 | classifiers=[ |
46 | 42 | 'Development Status :: 4 - Beta', |
47 | 43 | 'Environment :: Web Environment', |
51 | 47 | 'Operating System :: OS Independent', |
52 | 48 | 'Framework :: Django', |
53 | 49 | 'Programming Language :: Python', |
54 | 'Programming Language :: Python :: 2.7', | |
55 | 50 | 'Programming Language :: Python :: 3', |
56 | 'Programming Language :: Python :: 3.3', | |
57 | 51 | 'Programming Language :: Python :: 3.4', |
58 | 52 | 'Programming Language :: Python :: 3.5', |
59 | ] | |
53 | 'Programming Language :: Python :: 3.6', | |
54 | 'Programming Language :: Python :: 3.7', | |
55 | 'Programming Language :: Python :: 3 :: Only', | |
56 | ], | |
57 | project_urls={ | |
58 | 'Bug Reports': 'https://github.com/djangonauts/django-rest-framework-gis/issues', | |
59 | 'Continuous Integration': 'https://travis-ci.org/djangonauts/django-rest-framework-gis', | |
60 | 'Mailing List': 'https://groups.google.com/forum/#!forum/django-rest-framework-gis', | |
61 | 'Code Coverage': 'https://coveralls.io/github/djangonauts/django-rest-framework-gis', | |
62 | 'Source Code': 'https://github.com/djangonauts/django-rest-framework-gis', | |
63 | }, | |
60 | 64 | ) |
6 | 6 | import sys |
7 | 7 | import json |
8 | 8 | import pickle |
9 | ||
10 | from unittest import skipIf | |
9 | 11 | |
10 | 12 | from django.test import TestCase |
11 | 13 | from django.contrib.gis.geos import GEOSGeometry, Polygon, Point |
15 | 17 | from django.core.urlresolvers import reverse |
16 | 18 | from django.core.exceptions import ImproperlyConfigured |
17 | 19 | |
20 | import rest_framework | |
21 | ||
18 | 22 | from rest_framework_gis import serializers as gis_serializers |
19 | 23 | from rest_framework_gis.fields import GeoJsonDict |
20 | 24 | |
21 | 25 | from .models import Location, LocatedFile |
22 | 26 | from .serializers import LocationGeoSerializer |
27 | ||
28 | is_pre_drf_39 = not rest_framework.VERSION.startswith('3.9') | |
23 | 29 | |
24 | 30 | |
25 | 31 | class TestRestFrameworkGis(TestCase): |
471 | 477 | location = Location.objects.all()[0] |
472 | 478 | self.assertEqual(location.name, "HTML test WKT") |
473 | 479 | |
480 | @skipIf(is_pre_drf_39, 'Skip this test if DRF < 3.9') | |
474 | 481 | def test_geojson_HTML_widget_value(self): |
482 | self._create_locations() | |
483 | response = self.client.get(self.geojson_location_list_url, HTTP_ACCEPT='text/html') | |
484 | self.assertContains(response, '<textarea name="geometry"') | |
485 | self.assertContains(response, '"type": "Point"') | |
486 | self.assertContains(response, '"coordinates": [') | |
487 | ||
488 | @skipIf(not is_pre_drf_39, 'Skip this test if DRF >= 3.9') | |
489 | def test_geojson_HTML_widget_value_pre_drf_39(self): | |
475 | 490 | self._create_locations() |
476 | 491 | response = self.client.get(self.geojson_location_list_url, HTTP_ACCEPT='text/html') |
477 | 492 | self.assertContains(response, '<textarea name="geometry"') |