djangorestframework-gis-0.12/0000775000175000017500000000000013202036645017425 5ustar nemesisnemesis00000000000000djangorestframework-gis-0.12/requirements.txt0000664000175000017500000000004213202034134022674 0ustar nemesisnemesis00000000000000six djangorestframework>=3.3,<3.8 djangorestframework-gis-0.12/djangorestframework_gis.egg-info/0000775000175000017500000000000013202036645026037 5ustar nemesisnemesis00000000000000djangorestframework-gis-0.12/djangorestframework_gis.egg-info/SOURCES.txt0000664000175000017500000000242313202036645027724 0ustar nemesisnemesis00000000000000AUTHORS CHANGES.rst LICENSE MANIFEST.in README.rst requirements.txt runtests.py setup.cfg setup.py djangorestframework_gis.egg-info/PKG-INFO djangorestframework_gis.egg-info/SOURCES.txt djangorestframework_gis.egg-info/dependency_links.txt djangorestframework_gis.egg-info/requires.txt djangorestframework_gis.egg-info/top_level.txt rest_framework_gis/__init__.py rest_framework_gis/apps.py rest_framework_gis/fields.py rest_framework_gis/filters.py rest_framework_gis/filterset.py rest_framework_gis/pagination.py rest_framework_gis/serializers.py rest_framework_gis/tilenames.py tests/local_settings.example.py tests/local_settings.py tests/manage.py tests/settings.py tests/urls.py tests/django_restframework_gis_tests/__init__.py tests/django_restframework_gis_tests/admin.py tests/django_restframework_gis_tests/models.py tests/django_restframework_gis_tests/serializers.py tests/django_restframework_gis_tests/test_bbox.py tests/django_restframework_gis_tests/test_filters.py tests/django_restframework_gis_tests/test_performance.py tests/django_restframework_gis_tests/tests.py tests/django_restframework_gis_tests/urls.py tests/django_restframework_gis_tests/views.py tests/django_restframework_gis_tests/migrations/0001_initial.py tests/django_restframework_gis_tests/migrations/__init__.pydjangorestframework-gis-0.12/djangorestframework_gis.egg-info/top_level.txt0000664000175000017500000000002313202036643030562 0ustar nemesisnemesis00000000000000rest_framework_gis djangorestframework-gis-0.12/djangorestframework_gis.egg-info/requires.txt0000664000175000017500000000004213202036643030431 0ustar nemesisnemesis00000000000000six djangorestframework>=3.3,<3.8 djangorestframework-gis-0.12/djangorestframework_gis.egg-info/dependency_links.txt0000664000175000017500000000000113202036643032103 0ustar nemesisnemesis00000000000000 djangorestframework-gis-0.12/djangorestframework_gis.egg-info/PKG-INFO0000664000175000017500000000203213202036643027127 0ustar nemesisnemesis00000000000000Metadata-Version: 1.1 Name: djangorestframework-gis Version: 0.12 Summary: Geographic add-ons for Django Rest Framework Home-page: https://github.com/djangonauts/django-rest-framework-gis Author: Douglas Meehan Author-email: django-rest-framework-gis@googlegroups.com License: BSD Download-URL: https://github.com/djangonauts/django-rest-framework-gis/releases Description: UNKNOWN Keywords: django,rest-framework,gis,geojson Platform: Platform Indipendent Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Topic :: Internet :: WWW/HTTP Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Framework :: Django Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 djangorestframework-gis-0.12/README.rst0000664000175000017500000005476713202036224021130 0ustar nemesisnemesis00000000000000django-rest-framework-gis ========================= |Build Status| |Coverage Status| |Requirements Status| |PyPI version| Geographic add-ons for Django Rest Framework - `Mailing List `__. Install last stable version from pypi ------------------------------------- .. code-block:: bash pip install djangorestframework-gis Install development version --------------------------- .. code-block:: bash pip install https://github.com/djangonauts/django-rest-framework-gis/tarball/master Setup ----- Add ``rest_framework_gis`` in ``settings.INSTALLED_APPS``, after ``rest_framework``: .. code-block:: python INSTALLED_APPS = [ # ... 'rest_framework', 'rest_framework_gis', # ... ] Compatibility with DRF, Django and Python ----------------------------------------- =============== ============================ ==================== ================================== DRF-gis version DRF version Django version Python version **0.12.x** **3.1** to **3.7** **1.11** to **2.0** **2.7** to **3.6** **0.11.x** **3.1** to **3.6** **1.7** to **1.11** **2.7** to **3.6** **0.10.x** **3.1** to **3.3** **1.7** to **1.9** **2.7** to **3.5** **0.9.6** **3.1** to **3.2** **1.5** to **1.8** **2.6** to **3.5** **0.9.5** **3.1** to **3.2** **1.5** to **1.8** **2.6** to **3.4** **0.9.4** **3.1** to **3.2** **1.5** to **1.8** **2.6** to **3.4** **0.9.3** **3.1** **1.5** to **1.8** **2.6** to **3.4** **0.9.2** **3.1** **1.5** to **1.8** **2.6** to **3.4** **0.9.1** **3.1** **1.5** to **1.8** **2.6** to **3.4** **0.9** **3.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** **0.9** **3.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** **0.9** **3.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** **0.8.2** **3.0.4** to **3.1.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** **0.8.1** **3.0.4** to **3.1.1** **1.5** to **1.8** **2.6**, **2.7**, **3.3**, **3.4** **0.8** **3.0.4** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** **0.7** **2.4.3** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** **0.6** **2.4.3** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** **0.5** from **2.3.14** to **2.4.2** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** **0.4** from **2.3.14** to **2.4.2** **1.5** to **1.7** **2.6**, **2.7**, **3.3**, **3.4** **0.3** from **2.3.14** to **2.4.2** **1.5**, **1.6** **2.6**, **2.7** **0.2** from **2.2.2** to **2.3.13** **1.5**, **1.6** **2.6**, **2.7** =============== ============================ ==================== ================================== Fields ------ GeometryField ~~~~~~~~~~~~~ Provides a ``GeometryField``, which is a subclass of Django Rest Framework (from now on **DRF**) ``WritableField``. This field handles GeoDjango geometry fields, providing custom ``to_native`` and ``from_native`` methods for GeoJSON input/output. **New in 0.9.3:** there is no need to define this field explicitly in your serializer, it's mapped automatically during initialization in ``rest_framework_gis.apps.AppConfig.ready()``. GeometrySerializerMethodField ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides a ``GeometrySerializerMethodField``, which is a subclass of DRF ``SerializerMethodField`` and handles values which are computed with a serializer method and are used as a ``geo_field``. `See example below `__. Serializers ----------- GeoModelSerializer (DEPRECATED) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **Deprecated, will be removed in 1.0**: Using this serializer is not needed anymore since 0.9.3 if you add ``rest_framework_gis`` in ``settings.INSTALLED_APPS`` Provides a ``GeoModelSerializer``, which is a sublass of DRF ``ModelSerializer``. This serializer updates the field\_mapping dictionary to include field mapping of GeoDjango geometry fields to the above ``GeometryField``. For example, the following model: .. code-block:: python class Location(models.Model): """ A model which holds information about a particular location """ address = models.Charfield(max_length=255) city = models.CharField(max_length=100) state = models.CharField(max_length=100) point = models.PointField() By default, the DRF ModelSerializer will output: .. code-block:: javascript { "id": 1, "address": "742 Evergreen Terrace", "city": "Springfield", "state": "Oregon", "point": "POINT(-123.0208 44.0464)" } In contrast, the ``GeoModelSerializer`` will output: .. code-block:: javascript { "id": 1, "address": "742 Evergreen Terrace", "city": "Springfield", "state": "Oregon", "point": { "type": "Point", "coordinates": [-123.0208, 44.0464], } } GeoFeatureModelSerializer ~~~~~~~~~~~~~~~~~~~~~~~~~ ``GeoFeatureModelSerializer`` is a subclass of ``rest_framework.ModelSerializer`` which will output data in a format that is **GeoJSON** compatible. Using the above example, the ``GeoFeatureModelSerializer`` will output: .. code-block:: javascript { "id": 1, "type": "Feature", "geometry": { "point": { "type": "Point", "coordinates": [-123.0208, 44.0464], }, }, "properties": { "address": "742 Evergreen Terrace", "city": "Springfield", "state": "Oregon" } } If you are serializing an object list, ``GeoFeatureModelSerializer`` will create a ``FeatureCollection``: .. code-block:: javascript { "type": "FeatureCollection", "features": [ { "id": 1 "type": "Feature", "geometry": { "point": { "type": "Point", "coordinates": [-123.0208, 44.0464], } }, "properties": { "address": "742 Evergreen Terrace", "city": "Springfield", "state": "Oregon", } } { "id": 2, "type": "Feature", "geometry": { "point": { "type": "Point", "coordinates": [-123.0208, 44.0489], }, }, "properties": { "address": "744 Evergreen Terrace", "city": "Springfield", "state": "Oregon" } } } Specifying the geometry field: "geo_field" ########################################## ``GeoFeatureModelSerializer`` requires you to define a ``geo_field`` to be serialized as the "geometry". For example: .. code-block:: python from rest_framework_gis.serializers import GeoFeatureModelSerializer class LocationSerializer(GeoFeatureModelSerializer): """ A class to serialize locations as GeoJSON compatible data """ class Meta: model = Location geo_field = "point" # you can also explicitly declare which fields you want to include # as with a ModelSerializer. fields = ('id', 'address', 'city', 'state') Using GeometrySerializerMethodField as "geo_field" ################################################## ``geo_field`` may also be an instance of ``GeometrySerializerMethodField``. In this case you can compute its value during serialization. For example: .. code-block:: python from django.contrib.gis.geos import Point from rest_framework_gis.serializers import GeoFeatureModelSerializer, GeometrySerializerMethodField class LocationSerializer(GeoFeatureModelSerializer): """ A class to serialize locations as GeoJSON compatible data """ # a field which contains a geometry value and can be used as geo_field other_point = GeometrySerializerMethodField() def get_other_point(self, obj): return Point(obj.point.lat / 2, obj.point.lon / 2) class Meta: model = Location geo_field = 'other_point' Serializer for ``geo_field`` may also return ``None`` value, which will translate to ``null`` value for geojson ``geometry`` field. Specifying the ID: "id_field" ############################# The primary key of the model (usually the "id" attribute) is automatically used as the ``id`` field of each `GeoJSON Feature Object `_. The default behaviour follows the `GeoJSON RFC `_, but it can be disbaled by setting ``id_field`` to ``False``: .. code-block:: python from rest_framework_gis.serializers import GeoFeatureModelSerializer class LocationSerializer(GeoFeatureModelSerializer): class Meta: model = Location geo_field = "point" id_field = False fields = ('id', 'address', 'city', 'state') The ``id_field`` can also be set to use some other unique field in your model, eg: ``slug``: .. code-block:: python from rest_framework_gis.serializers import GeoFeatureModelSerializer class LocationSerializer(GeoFeatureModelSerializer): class Meta: model = Location geo_field = 'point' id_field = 'slug' fields = ('slug', 'address', 'city', 'state') Bounding Box: "auto_bbox" and "bbox_geo_field" ############################################## The GeoJSON specification allows a feature to contain a `boundingbox of a feature `__. ``GeoFeatureModelSerializer`` allows two different ways to fill this property. The first is using the ``geo_field`` to calculate the bounding box of a feature. This only allows read access for a REST client and can be achieved using ``auto_bbox``. Example: .. code-block:: python from rest_framework_gis.serializers import GeoFeatureModelSerializer class LocationSerializer(GeoFeatureModelSerializer): class Meta: model = Location geo_field = 'geometry' auto_bbox = True The second approach uses the ``bbox_geo_field`` to specify an additional ``GeometryField`` of the model which will be used to calculate the bounding box. This allows boundingboxes differ from the exact extent of a features geometry. Additionally this enables read and write access for the REST client. Bounding boxes send from the client will be saved as Polygons. Example: .. code-block:: python from rest_framework_gis.serializers import GeoFeatureModelSerializer class LocationSerializer(GeoFeatureModelSerializer): class Meta: model = BoxedLocation geo_field = 'geometry' bbox_geo_field = 'bbox_geometry' Custom GeoJSON properties source ################################ In GeoJSON each feature can have a ``properties`` member containing the attributes of the feature. By default this field is filled with the attributes from your Django model, excluding the id, geometry and bounding box fields. It's possible to override this behaviour and implement a custom source for the ``properties`` member. The following example shows how to use a PostgreSQL HStore field as a source for the ``properties`` member: .. code-block:: python # models.py class Link(models.Model): """ Metadata is stored in a PostgreSQL HStore field, which allows us to store arbitrary key-value pairs with a link record. """ metadata = HStoreField(blank=True, null=True, default=dict) geo = models.LineStringField() objects = models.GeoManager() # serializers.py class NetworkGeoSerializer(GeoFeatureModelSerializer): class Meta: model = models.Link geo_field = 'geo' auto_bbox = True def get_properties(self, instance, fields): # This is a PostgreSQL HStore field, which django maps to a dict return instance.metadata def unformat_geojson(self, feature): attrs = { self.Meta.geo_field: feature["geometry"], "metadata": feature["properties"] } if self.Meta.bbox_geo_field and "bbox" in feature: attrs[self.Meta.bbox_geo_field] = Polygon.from_bbox(feature["bbox"]) return attrs When the serializer renders GeoJSON, it calls the method ``get_properties`` for each object in the database. This function should return a dictionary containing the attributes for the feature. In the case of a HStore field, this function is easily implemented. The reverse is also required: mapping a GeoJSON formatted structure to attributes of your model. This task is done by ``unformat_geojson``. It should return a dictionary with your model attributes as keys, and the corresponding values retrieved from the GeoJSON feature data. Pagination ---------- We provide a ``GeoJsonPagination`` class. GeoJsonPagination ~~~~~~~~~~~~~~~~~ Based on ``rest_framework.pagination.PageNumberPagination``. Code example: .. code-block:: python from rest_framework_gis.pagination import GeoJsonPagination # --- other omitted imports --- # class GeojsonLocationList(generics.ListCreateAPIView): # -- other omitted view attributes --- # pagination_class = GeoJsonPagination Example result response (cut to one element only instead of 10): .. code-block:: javascript { "type": "FeatureCollection", "count": 25, "next": "http://localhost:8000/geojson/?page=2", "previous": null, "features": [ { "type": "Feature", "geometry": { "type": "Point", "coordinates": [ 42.0, 50.0 ] }, "properties": { "name": "test" } } ] } Filters ------- **note**: this feature has been tested up to django-filter 1.0. We provide a ``GeometryFilter`` field as well as a ``GeoFilterSet`` for usage with ``django_filter``. You simply provide, in the query string, one of the textual types supported by ``GEOSGeometry``. By default, this includes WKT, HEXEWKB, WKB (in a buffer), and GeoJSON. GeometryFilter ~~~~~~~~~~~~~~ .. code-block:: python from rest_framework_gis.filterset import GeoFilterSet from rest_framework_gis.filters import GeometryFilter from django_filters import filters class RegionFilter(GeoFilterSet): slug = filters.CharFilter(name='slug', lookup_expr='istartswith') contains_geom = GeometryFilter(name='geom', lookup_expr='contains') class Meta: model = Region We can then filter in the URL, using GeoJSON, and we will perform a ``__contains`` geometry lookup, e.g. ``/region/?contains_geom={ "type": "Point", "coordinates": [ -123.26436996459961, 44.564178042345375 ] }``. GeoFilterSet ~~~~~~~~~~~~ The ``GeoFilterSet`` provides a ``django_filter`` compatible ``FilterSet`` that will automatically create ``GeometryFilters`` for ``GeometryFields``. InBBoxFilter ~~~~~~~~~~~~ Provides a ``InBBoxFilter``, which is a subclass of DRF ``BaseFilterBackend``. Filters a queryset to only those instances within a certain bounding box. ``views.py:`` .. code-block:: python from rest_framework_gis.filters import InBBoxFilter class LocationList(ListAPIView): queryset = models.Location.objects.all() serializer_class = serializers.LocationSerializer bbox_filter_field = 'point' filter_backends = (InBBoxFilter, ) bbox_filter_include_overlapping = True # Optional We can then filter in the URL, using Bounding Box format (min Lon, min Lat, max Lon, max Lat), and we can search for instances within the bounding box, e.g.: ``/location/?in_bbox=-90,29,-89,35``. By default, InBBoxFilter will only return those instances entirely within the stated bounding box. To include those instances which overlap the bounding box, include ``bbox_filter_include_overlapping = True`` in your view. Note that if you are using other filters, you'll want to include your other filter backend in your view. For example: ``filter_backends = (InBBoxFilter, DjangoFilterBackend,)`` TMSTileFilter ~~~~~~~~~~~~~ Provides a ``TMSTileFilter``, which is a subclass of ``InBBoxFilter``. Filters a queryset to only those instances within a bounding box defined by a `TMS tile `__ address. ``views.py:`` .. code-block:: python from rest_framework_gis.filters import TMSTileFilter class LocationList(ListAPIView): queryset = models.Location.objects.all() serializer_class = serializers.LocationSerializer bbox_filter_field = 'point' filter_backends = (TMSTileFilter, ) bbox_filter_include_overlapping = True # Optional We can then filter in the URL, using TMS tile addresses in the zoom/x/y format, eg:. ``/location/?tile=8/100/200`` which is equivalant to filtering on the bbox (-39.37500,-71.07406,-37.96875,-70.61261). For more information on configuration options see InBBoxFilter. Note that the tile address start in the upper left, not the lower left origin used by some implementations. DistanceToPointFilter ~~~~~~~~~~~~~~~~~~~~~ Provides a ``DistanceToPointFilter``, which is a subclass of DRF ``BaseFilterBackend``. Filters a queryset to only those instances within a certain distance of a given point. ``views.py:`` .. code-block:: python from rest_framework_gis.filters import DistanceToPointFilter class LocationList(ListAPIView): queryset = models.Location.objects.all() serializer_class = serializers.LocationSerializer distance_filter_field = 'geometry' filter_backends = (DistanceToPointFilter, ) bbox_filter_include_overlapping = True # Optional We can then filter in the URL, using a distance and a point in (lon, lat) format. The distance can be given in meters or in degrees. eg:. ``/location/?dist=4000&point=-122.4862,37.7694&format=json`` which is equivalant to filtering within 4000 meters of the point (-122.4862, 37.7694). By default, DistanceToPointFilter will pass the 'distance' in the URL directly to the database for the search. The effect depends on the srid of the database in use. If geo data is indexed in meters (srid 3875, aka 900913), a distance in meters can be passed in directly without conversion. For lat-lon databases such as srid 4326, which is indexed in degrees, the 'distance' will be interpreted as degrees. Set the flag, 'distance_filter_convert_meters' to 'True' in order to convert an input distance in meters to degrees. This conversion is approximate, and the errors at latitudes > 60 degrees are > 25%. Projects using this package --------------------------- - `Nodeshot `__: Extensible Django web application for management of community-led georeferenced data Running the tests ----------------- Required setup ============== You need one of the `Spatial Database servers supported by GeoDjango `__, and create a database for the tests. The following can be used with PostgreSQL: .. code-block:: bash createdb django_restframework_gis psql -U postgres -d django_restframework_gis -c "CREATE EXTENSION postgis" You might need to tweak the DB settings according to your DB configuration. You can copy the file ``local_settings.example.py`` to ``local_settings.py`` and change the ``DATABASES`` and/or ``INSTALLED_APPS`` directives there. This should allow you to run the tests already. For reference, the following steps will setup a development environment for contributing to the project: - create a spatial database named "django\_restframework\_gis" - create ``local_settings.py``, eg: ``cp local_settings.example.py local_settings.py`` - tweak the ``DATABASES`` configuration directive according to your DB settings - optionally install ``olwidget`` with ``pip install olwidget`` - uncomment ``INSTALLED_APPS`` (remove olwidget if you did not install it) - run ``python manage.py syncdb`` - run ``python manage.py collectstatic`` - run ``python manage.py runserver`` Using tox ========= The recommended way to run the tests is by using `tox `__, which can be installed using `pip install tox`. You can use ``tox -l`` to list the available environments, and then e.g. use the following to run all tests with Python 3.6 and Django 1.11: .. code-block:: bash tox -e py36-django111 By default Django's test runner is used, but there is a variation of tox's envlist to use pytest (using the ``-pytest`` suffix). You can pass optional arguments to the test runner like this: .. code-block:: bash tox -e py36-django111-pytest -- -k test_foo Running tests manually ====================== Please refer to the ``tox.ini`` file for reference/help in case you want to run tests manually / without tox. Contributing ------------ 1. Join the `Django REST Framework GIS Mailing List `__ and announce your intentions 2. Follow the `PEP8 Style Guide for Python Code `__ 3. Fork this repo 4. Write code 5. Write tests for your code 6. Ensure all tests pass 7. Ensure test coverage is not under 90% 8. Document your changes 9. Send pull request .. |Build Status| image:: https://travis-ci.org/djangonauts/django-rest-framework-gis.svg?branch=master :target: https://travis-ci.org/djangonauts/django-rest-framework-gis .. |Coverage Status| image:: https://coveralls.io/repos/djangonauts/django-rest-framework-gis/badge.svg :target: https://coveralls.io/r/djangonauts/django-rest-framework-gis .. |Requirements Status| image:: https://requires.io/github/djangonauts/django-rest-framework-gis/requirements.svg?branch=master :target: https://requires.io/github/djangonauts/django-rest-framework-gis/requirements/?branch=master .. |PyPI version| image:: https://badge.fury.io/py/djangorestframework-gis.svg :target: http://badge.fury.io/py/djangorestframework-gis djangorestframework-gis-0.12/setup.cfg0000664000175000017500000000013013202036645021240 0ustar nemesisnemesis00000000000000[bdist_wheel] universal = 1 [egg_info] tag_date = 0 tag_svn_revision = 0 tag_build = djangorestframework-gis-0.12/AUTHORS0000664000175000017500000000053412677522234020510 0ustar nemesisnemesis00000000000000Original Author --------------- Douglas Meehan https://github.com/dmeehan Contributors ------------ Christoph Heer https://github.com/jarus Federico Capoano https://github.com/nemesisdesign/ Shanto https://github.com/Shanto Eric Theise https://github.com/erictheise Asif Saifuddin https://github.com/auvipy djangorestframework-gis-0.12/LICENSE0000664000175000017500000000204612544034142020432 0ustar nemesisnemesis00000000000000Copyright (C) 2013-2014 Douglas Meehan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.djangorestframework-gis-0.12/rest_framework_gis/0000775000175000017500000000000013202036645023321 5ustar nemesisnemesis00000000000000djangorestframework-gis-0.12/rest_framework_gis/pagination.py0000664000175000017500000000115412643201066026024 0ustar nemesisnemesis00000000000000from collections import OrderedDict from rest_framework import pagination from rest_framework.response import Response class GeoJsonPagination(pagination.PageNumberPagination): """ A geoJSON implementation of a pagination serializer. """ page_size_query_param = 'page_size' def get_paginated_response(self, data): return Response(OrderedDict([ ('type', 'FeatureCollection'), ('count', self.page.paginator.count), ('next', self.get_next_link()), ('previous', self.get_previous_link()), ('features', data['features']) ])) djangorestframework-gis-0.12/rest_framework_gis/__init__.py0000664000175000017500000000070713202036062025427 0ustar nemesisnemesis00000000000000VERSION = (0, 12, 0, 'final') __version__ = VERSION # alias def get_version(): version = '%s.%s' % (VERSION[0], VERSION[1]) if VERSION[2]: version = '%s.%s' % (version, VERSION[2]) if VERSION[3:] == ('alpha', 0): version = '%s pre-alpha' % version else: if VERSION[3] != 'final': version = '%s %s' % (version, VERSION[3]) return version default_app_config = 'rest_framework_gis.apps.AppConfig' djangorestframework-gis-0.12/rest_framework_gis/filterset.py0000664000175000017500000000005212544034142025667 0ustar nemesisnemesis00000000000000from .filters import GeoFilterSet # noqa djangorestframework-gis-0.12/rest_framework_gis/apps.py0000664000175000017500000000212012643201066024630 0ustar nemesisnemesis00000000000000from django.apps import AppConfig as BaseConfig class AppConfig(BaseConfig): name = 'rest_framework_gis' def ready(self): """ update Django Rest Framework serializer mappings """ from django.contrib.gis.db import models from rest_framework.serializers import ModelSerializer from .fields import GeometryField try: # drf 3.0 field_mapping = ModelSerializer._field_mapping.mapping except AttributeError: # drf 3.1 field_mapping = ModelSerializer.serializer_field_mapping # map GeoDjango fields to drf-gis GeometryField field_mapping.update({ models.GeometryField: GeometryField, models.PointField: GeometryField, models.LineStringField: GeometryField, models.PolygonField: GeometryField, models.MultiPointField: GeometryField, models.MultiLineStringField: GeometryField, models.MultiPolygonField: GeometryField, models.GeometryCollectionField: GeometryField }) djangorestframework-gis-0.12/rest_framework_gis/tilenames.py0000664000175000017500000000200512544034142025647 0ustar nemesisnemesis00000000000000#!/usr/bin/env python # ------------------------------------------------------- # Translates between lat/long and the slippy-map tile # numbering scheme # # http://wiki.openstreetmap.org/index.php/Slippy_map_tilenames # # Written by Oliver White, 2007 # This file is public-domain # ------------------------------------------------------- from math import pi, atan, sinh, degrees, pow as math_pow def num_tiles(z): return(math_pow(2, z)) def lat_edges(y, z): n = num_tiles(z) unit = 1 / n relY1 = y * unit relY2 = relY1 + unit lat1 = mercator_to_lat(pi * (1 - 2 * relY1)) lat2 = mercator_to_lat(pi * (1 - 2 * relY2)) return(lat1, lat2) def lon_edges(x, z): n = num_tiles(z) unit = 360 / n lon1 = -180 + x * unit lon2 = lon1 + unit return(lon1, lon2) def tile_edges(x, y, z): lat1, lat2 = lat_edges(y, z) lon1, lon2 = lon_edges(x, z) return((lon1, lat2, lon2, lat1)) # w, s, e, n def mercator_to_lat(mercatorY): return(degrees(atan(sinh(mercatorY)))) djangorestframework-gis-0.12/rest_framework_gis/serializers.py0000664000175000017500000001641413103037154026231 0ustar nemesisnemesis00000000000000from collections import OrderedDict from django.core.exceptions import ImproperlyConfigured from django.contrib.gis.geos import Polygon from rest_framework.serializers import ModelSerializer, ListSerializer, LIST_SERIALIZER_KWARGS from .fields import GeometryField, GeometrySerializerMethodField # noqa class GeoModelSerializer(ModelSerializer): """ Deprecated, will be removed in django-rest-framework-gis 1.0 """ class GeoFeatureModelListSerializer(ListSerializer): @property def data(self): return super(ListSerializer, self).data def to_representation(self, data): """ Add GeoJSON compatible formatting to a serialized queryset list """ return OrderedDict(( ("type", "FeatureCollection"), ("features", super(GeoFeatureModelListSerializer, self).to_representation(data)) )) class GeoFeatureModelSerializer(ModelSerializer): """ A subclass of ModelSerializer that outputs geojson-ready data as features and feature collections """ @classmethod def many_init(cls, *args, **kwargs): child_serializer = cls(*args, **kwargs) list_kwargs = {'child': child_serializer} list_kwargs.update(dict([ (key, value) for key, value in kwargs.items() if key in LIST_SERIALIZER_KWARGS ])) meta = getattr(cls, 'Meta', None) list_serializer_class = getattr(meta, 'list_serializer_class', GeoFeatureModelListSerializer) return list_serializer_class(*args, **list_kwargs) def __init__(self, *args, **kwargs): super(GeoFeatureModelSerializer, self).__init__(*args, **kwargs) meta = getattr(self, 'Meta') default_id_field = None primary_key = self.Meta.model._meta.pk.name # use primary key as id_field when possible if not hasattr(meta, 'fields') or meta.fields == '__all__' or primary_key in meta.fields: default_id_field = primary_key meta.id_field = getattr(meta, 'id_field', default_id_field) if not hasattr(meta, 'geo_field') or not meta.geo_field: raise ImproperlyConfigured("You must define a 'geo_field'.") def check_excludes(field_name, field_role): """make sure the field is not excluded""" if hasattr(meta, 'exclude') and field_name in meta.exclude: raise ImproperlyConfigured("You cannot exclude your '{0}'.".format(field_role)) def add_to_fields(field_name): """Make sure the field is included in the fields""" if hasattr(meta, 'fields') and meta.fields != '__all__': if field_name not in meta.fields: if type(meta.fields) is tuple: additional_fields = (field_name,) else: additional_fields = [field_name] meta.fields += additional_fields check_excludes(meta.geo_field, 'geo_field') add_to_fields(meta.geo_field) meta.bbox_geo_field = getattr(meta, 'bbox_geo_field', None) if meta.bbox_geo_field: check_excludes(meta.bbox_geo_field, 'bbox_geo_field') add_to_fields(meta.bbox_geo_field) meta.auto_bbox = getattr(meta, 'auto_bbox', False) if meta.bbox_geo_field and meta.auto_bbox: raise ImproperlyConfigured("You must eiher define a 'bbox_geo_field' or " "'auto_bbox', but you can not set both") def to_representation(self, instance): """ Serialize objects -> primitives. """ # prepare OrderedDict geojson structure feature = OrderedDict() # the list of fields that will be processed by get_properties # we will remove fields that have been already processed # to increase performance on large numbers fields = list(self.fields.values()) # optional id attribute if self.Meta.id_field: field = self.fields[self.Meta.id_field] value = field.get_attribute(instance) feature["id"] = field.to_representation(value) fields.remove(field) # required type attribute # must be "Feature" according to GeoJSON spec feature["type"] = "Feature" # required geometry attribute # MUST be present in output according to GeoJSON spec field = self.fields[self.Meta.geo_field] geo_value = field.get_attribute(instance) feature["geometry"] = field.to_representation(geo_value) fields.remove(field) # Bounding Box # if auto_bbox feature is enabled # bbox will be determined automatically automatically if self.Meta.auto_bbox and geo_value: feature["bbox"] = geo_value.extent # otherwise it can be determined via another field elif self.Meta.bbox_geo_field: field = self.fields[self.Meta.bbox_geo_field] value = field.get_attribute(instance) feature["bbox"] = value.extent if hasattr(value, 'extent') else None fields.remove(field) # GeoJSON properties feature["properties"] = self.get_properties(instance, fields) return feature def get_properties(self, instance, fields): """ Get the feature metadata which will be used for the GeoJSON "properties" key. By default it returns all serializer fields excluding those used for the ID, the geometry and the bounding box. :param instance: The current Django model instance :param fields: The list of fields to process (fields already processed have been removed) :return: OrderedDict containing the properties of the current feature :rtype: OrderedDict """ properties = OrderedDict() for field in fields: if field.write_only: continue value = field.get_attribute(instance) representation = None if value is not None: representation = field.to_representation(value) properties[field.field_name] = representation return properties def to_internal_value(self, data): """ Override the parent method to first remove the GeoJSON formatting """ if 'properties' in data: data = self.unformat_geojson(data) return super(GeoFeatureModelSerializer, self).to_internal_value(data) def unformat_geojson(self, feature): """ This function should return a dictionary containing keys which maps to serializer fields. Remember that GeoJSON contains a key "properties" which contains the feature metadata. This should be flattened to make sure this metadata is stored in the right serializer fields. :param feature: The dictionary containing the feature data directly from the GeoJSON data. :return: A new dictionary which maps the GeoJSON values to serializer fields """ attrs = feature["properties"] if 'geometry' in feature: attrs[self.Meta.geo_field] = feature['geometry'] if self.Meta.bbox_geo_field and 'bbox' in feature: attrs[self.Meta.bbox_geo_field] = Polygon.from_bbox(feature['bbox']) return attrs djangorestframework-gis-0.12/rest_framework_gis/fields.py0000664000175000017500000000570013202034134025132 0ustar nemesisnemesis00000000000000import six # TODO Remove this along with GeoJsonDict when support for python 2.6/2.7 is dropped. import json from collections import OrderedDict from django.contrib.gis.geos import GEOSGeometry, GEOSException from django.contrib.gis.gdal import GDALException from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ from rest_framework.fields import Field, SerializerMethodField __all__ = ['GeometryField', 'GeometrySerializerMethodField'] class GeometryField(Field): """ A field to handle GeoDjango Geometry fields """ type_name = 'GeometryField' def __init__(self, **kwargs): super(GeometryField, self).__init__(**kwargs) self.style = {'base_template': 'textarea.html'} def to_representation(self, value): if isinstance(value, dict) or value is None: return value # we expect value to be a GEOSGeometry instance return GeoJsonDict(value.geojson) def to_internal_value(self, value): if value == '' or value is None: return value if isinstance(value, GEOSGeometry): # value already has the correct representation return value if isinstance(value, dict): value = json.dumps(value) try: return GEOSGeometry(value) except (ValueError, GEOSException, GDALException, TypeError): raise ValidationError(_('Invalid format: string or unicode input unrecognized as GeoJSON, WKT EWKT or HEXEWKB.')) def validate_empty_values(self, data): if data == '': self.fail('required') return super(GeometryField, self).validate_empty_values(data) class GeometrySerializerMethodField(SerializerMethodField): def to_representation(self, value): value = super(GeometrySerializerMethodField, self).to_representation(value) if value is not None: # we expect value to be a GEOSGeometry instance return GeoJsonDict(value.geojson) else: return None class GeoJsonDict(OrderedDict): """ Used for serializing GIS values to GeoJSON values TODO: remove this when support for python 2.6/2.7 will be dropped """ def __init__(self, *args, **kwargs): """ If a string is passed attempt to pass it through json.loads, because it should be a geojson formatted string. """ if args and isinstance(args[0], six.string_types): try: geojson = json.loads(args[0]) args = (geojson,) except ValueError: pass super(GeoJsonDict, self).__init__(*args, **kwargs) def __str__(self): """ Avoid displaying strings like ``{ 'type': u'Point', 'coordinates': [12, 32] }`` in DRF browsable UI inputs (python 2.6/2.7) see: https://github.com/djangonauts/django-rest-framework-gis/pull/60 """ return json.dumps(self) djangorestframework-gis-0.12/rest_framework_gis/filters.py0000664000175000017500000001512613202034134025337 0ustar nemesisnemesis00000000000000from math import cos, pi from django.db.models import Q from django.core.exceptions import ImproperlyConfigured from django.contrib.gis.db import models from django.contrib.gis.geos import Polygon, Point from django.contrib.gis import forms from rest_framework.filters import BaseFilterBackend from rest_framework.exceptions import ParseError from .tilenames import tile_edges try: import django_filters except ImportError: # pragma: no cover raise ImproperlyConfigured( 'restframework-gis filters depend on package "django-filter" ' 'which is missing. Install with "pip install django-filter".' ) try: # Django >= 2.0 from django.contrib.gis.db.models.fields import BaseSpatialField except ImportError: try: # pragma: no cover # django >= 1.8,<2.0 from django.contrib.gis.db.models.lookups import gis_lookups except ImportError: # pragma: no cover # django <= 1.7 gis_lookups = models.sql.query.ALL_TERMS else: gis_lookups = BaseSpatialField.get_lookups() __all__ = [ 'InBBoxFilter', 'InBBOXFilter', 'GeometryFilter', 'GeoFilterSet', 'TMSTileFilter', 'DistanceToPointFilter' ] class InBBoxFilter(BaseFilterBackend): bbox_param = 'in_bbox' # The URL query parameter which contains the bbox. def get_filter_bbox(self, request): bbox_string = request.query_params.get(self.bbox_param, None) if not bbox_string: return None try: p1x, p1y, p2x, p2y = (float(n) for n in bbox_string.split(',')) except ValueError: raise ParseError('Invalid bbox string supplied for parameter {0}'.format(self.bbox_param)) x = Polygon.from_bbox((p1x, p1y, p2x, p2y)) return x def filter_queryset(self, request, queryset, view): filter_field = getattr(view, 'bbox_filter_field', None) include_overlapping = getattr(view, 'bbox_filter_include_overlapping', False) if include_overlapping: geoDjango_filter = 'bboverlaps' else: geoDjango_filter = 'contained' if not filter_field: return queryset bbox = self.get_filter_bbox(request) if not bbox: return queryset return queryset.filter(Q(**{'%s__%s' % (filter_field, geoDjango_filter): bbox})) # backward compatibility InBBOXFilter = InBBoxFilter class GeometryFilter(django_filters.Filter): field_class = forms.GeometryField def __init__(self, *args, **kwargs): kwargs.setdefault('widget', forms.BaseGeometryWidget) super(GeometryFilter, self).__init__(*args, **kwargs) class GeoFilterSet(django_filters.FilterSet): GEOFILTER_FOR_DBFIELD_DEFAULTS = { models.GeometryField: { 'filter_class': GeometryFilter }, } def __new__(cls, *args, **kwargs): try: cls._meta.filter_overrides.update(cls.GEOFILTER_FOR_DBFIELD_DEFAULTS) # maintain compatibility for django-filter < 0.15 except AttributeError: # pragma: nocover cls.filter_overrides.update(cls.GEOFILTER_FOR_DBFIELD_DEFAULTS) cls.LOOKUP_TYPES = sorted(gis_lookups) return super(GeoFilterSet, cls).__new__(cls) class TMSTileFilter(InBBoxFilter): tile_param = 'tile' # The URL query paramater which contains the tile address def get_filter_bbox(self, request): tile_string = request.query_params.get(self.tile_param, None) if not tile_string: return None try: z, x, y = (int(n) for n in tile_string.split('/')) except ValueError: raise ParseError('Invalid tile string supplied for parameter {0}'.format(self.tile_param)) bbox = Polygon.from_bbox(tile_edges(x, y, z)) return bbox class DistanceToPointFilter(BaseFilterBackend): dist_param = 'dist' point_param = 'point' # The URL query parameter which contains the def get_filter_point(self, request): point_string = request.query_params.get(self.point_param, None) if not point_string: return None try: (x, y) = (float(n) for n in point_string.split(',')) except ValueError: raise ParseError('Invalid geometry string supplied for parameter {0}'.format(self.point_param)) p = Point(x, y) return p def dist_to_deg(self, distance, latitude): """ distance = distance in meters latitude = latitude in degrees at the equator, the distance of one degree is equal in latitude and longitude. at higher latitudes, a degree longitude is shorter in length, proportional to cos(latitude) http://en.wikipedia.org/wiki/Decimal_degrees This function is part of a distance filter where the database 'distance' is in degrees. There's no good single-valued answer to this problem. The distance/ degree is quite constant N/S around the earth (latitude), but varies over a huge range E/W (longitude). Split the difference: I'm going to average the the degrees latitude and degrees longitude corresponding to the given distance. At high latitudes, this will be too short N/S and too long E/W. It splits the errors between the two axes. Errors are < 25 percent for latitudes < 60 degrees N/S. """ # d * (180 / pi) / earthRadius ==> degrees longitude # (degrees longitude) / cos(latitude) ==> degrees latitude lat = latitude if latitude >= 0 else -1 * latitude rad2deg = 180 / pi earthRadius = 6378160.0 latitudeCorrection = 0.5 * (1 + cos(lat * pi / 180)) return (distance / (earthRadius * latitudeCorrection) * rad2deg) def filter_queryset(self, request, queryset, view): filter_field = getattr(view, 'distance_filter_field', None) convert_distance_input = getattr(view, 'distance_filter_convert_meters', False) geoDjango_filter = 'dwithin' # use dwithin for points if not filter_field: return queryset point = self.get_filter_point(request) if not point: return queryset # distance in meters dist_string = request.query_params.get(self.dist_param, 1000) try: dist = float(dist_string) except ValueError: raise ParseError('Invalid distance string supplied for parameter {0}'.format(self.dist_param)) if (convert_distance_input): # Warning: assumes that the point is (lon,lat) dist = self.dist_to_deg(dist, point[1]) return queryset.filter(Q(**{'%s__%s' % (filter_field, geoDjango_filter): (point, dist)})) djangorestframework-gis-0.12/runtests.py0000755000175000017500000000060212526372137021674 0ustar nemesisnemesis00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import os import sys sys.path.insert(0, "tests") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") if __name__ == "__main__": from django.core.management import execute_from_command_line args = sys.argv args.insert(1, "test") args.insert(2, "django_restframework_gis_tests") execute_from_command_line(args) djangorestframework-gis-0.12/setup.py0000664000175000017500000000416613103043347021143 0ustar nemesisnemesis00000000000000#!/usr/bin/env python import sys import os from setuptools import setup, find_packages from rest_framework_gis import get_version def get_install_requires(): """ parse requirements.txt, ignore links, exclude comments """ requirements = [] for line in open('requirements.txt').readlines(): # skip to next iteration if comment or empty line if line.startswith('#') or line == '' or line.startswith('http') or line.startswith('git'): continue # add line to requirements requirements.append(line) return requirements if sys.argv[-1] == 'publish': os.system("python setup.py sdist bdist_wheel") os.system("twine upload -s dist/*") os.system("rm -rf dist build") args = {'version': get_version()} print("You probably want to also tag the version now:") print(" git tag -a %(version)s -m 'version %(version)s'" % args) print(" git push --tags") sys.exit() setup( name='djangorestframework-gis', version=get_version(), license='BSD', author='Douglas Meehan', author_email='django-rest-framework-gis@googlegroups.com', description='Geographic add-ons for Django Rest Framework', url='https://github.com/djangonauts/django-rest-framework-gis', download_url='https://github.com/djangonauts/django-rest-framework-gis/releases', platforms=['Platform Indipendent'], keywords=['django', 'rest-framework', 'gis', 'geojson'], packages=find_packages(exclude=['tests', 'tests.*']), install_requires=get_install_requires(), classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', 'Topic :: Internet :: WWW/HTTP', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Framework :: Django', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', ] ) djangorestframework-gis-0.12/PKG-INFO0000664000175000017500000000203213202036645020517 0ustar nemesisnemesis00000000000000Metadata-Version: 1.1 Name: djangorestframework-gis Version: 0.12 Summary: Geographic add-ons for Django Rest Framework Home-page: https://github.com/djangonauts/django-rest-framework-gis Author: Douglas Meehan Author-email: django-rest-framework-gis@googlegroups.com License: BSD Download-URL: https://github.com/djangonauts/django-rest-framework-gis/releases Description: UNKNOWN Keywords: django,rest-framework,gis,geojson Platform: Platform Indipendent Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Topic :: Internet :: WWW/HTTP Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Framework :: Django Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 djangorestframework-gis-0.12/MANIFEST.in0000664000175000017500000000022112643200322021147 0ustar nemesisnemesis00000000000000include AUTHORS include LICENSE include README.rst include CHANGES.rst include requirements.txt include runtests.py recursive-include tests *.py djangorestframework-gis-0.12/tests/0000775000175000017500000000000013202036645020567 5ustar nemesisnemesis00000000000000djangorestframework-gis-0.12/tests/settings.py0000664000175000017500000000405512703376214023011 0ustar nemesisnemesis00000000000000import os TEST_PERFORMANCE = False DEBUG = True TEMPLATE_DEBUG = DEBUG DATABASES = { 'default': { 'ENGINE': 'django.contrib.gis.db.backends.postgis', 'NAME': 'django_restframework_gis', 'USER': 'postgres', 'PASSWORD': 'postgres', 'HOST': '', 'PORT': '' }, } SECRET_KEY = 'fn)t*+$)ugeyip6-#txyy$5wf2ervc0d2n#h)qb)y5@ly$t*@w' INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.admin', # rest framework 'rest_framework', 'rest_framework_gis', # test app 'django_restframework_gis_tests' ) MIDDLEWARE_CLASSES = [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] ROOT_URLCONF = 'urls' TIME_ZONE = 'Europe/Rome' LANGUAGE_CODE = 'en-gb' USE_TZ = True USE_I18N = False USE_L10N = False SITE_ROOT = os.path.dirname(os.path.realpath(__file__)) MEDIA_ROOT = '%s/media/' % SITE_ROOT MEDIA_URL = '/media/' STATIC_ROOT = '%s/static/' % SITE_ROOT STATIC_URL = '/static/' TEMPLATE_STRING_IF_INVALID = 'INVALID_TEMPLATE: %s END_INVALID_TEMPLATE' # local settings must be imported before test runner otherwise they'll be ignored try: from local_settings import * except ImportError: pass djangorestframework-gis-0.12/tests/django_restframework_gis_tests/0000775000175000017500000000000013202036645027070 5ustar nemesisnemesis00000000000000djangorestframework-gis-0.12/tests/django_restframework_gis_tests/__init__.py0000664000175000017500000000000012552217421031167 0ustar nemesisnemesis00000000000000djangorestframework-gis-0.12/tests/django_restframework_gis_tests/migrations/0000775000175000017500000000000013202036645031244 5ustar nemesisnemesis00000000000000djangorestframework-gis-0.12/tests/django_restframework_gis_tests/migrations/__init__.py0000664000175000017500000000000012700422405033336 0ustar nemesisnemesis00000000000000djangorestframework-gis-0.12/tests/django_restframework_gis_tests/migrations/0001_initial.py0000664000175000017500000000443312700422405033706 0ustar nemesisnemesis00000000000000# -*- coding: utf-8 -*- # Generated by Django 1.9.4 on 2016-04-01 17:00 from __future__ import unicode_literals import django.contrib.gis.db.models.fields from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='BoxedLocation', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=32)), ('slug', models.SlugField(blank=True, max_length=128, unique=True)), ('timestamp', models.DateTimeField(blank=True, null=True)), ('geometry', django.contrib.gis.db.models.fields.GeometryField(srid=4326)), ('bbox_geometry', django.contrib.gis.db.models.fields.PolygonField(srid=4326)), ], options={ 'abstract': False, }, ), migrations.CreateModel( name='LocatedFile', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=32)), ('slug', models.SlugField(blank=True, max_length=128, unique=True)), ('timestamp', models.DateTimeField(blank=True, null=True)), ('geometry', django.contrib.gis.db.models.fields.GeometryField(srid=4326)), ('file', models.FileField(blank=True, null=True, upload_to='located_files')), ], options={ 'abstract': False, }, ), migrations.CreateModel( name='Location', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=32)), ('slug', models.SlugField(blank=True, max_length=128, unique=True)), ('timestamp', models.DateTimeField(blank=True, null=True)), ('geometry', django.contrib.gis.db.models.fields.GeometryField(srid=4326)), ], options={ 'abstract': False, }, ), ] djangorestframework-gis-0.12/tests/django_restframework_gis_tests/views.py0000664000175000017500000001337413202034134030576 0ustar nemesisnemesis00000000000000from django_filters.rest_framework import DjangoFilterBackend from rest_framework import generics from rest_framework_gis.filters import * from rest_framework_gis.pagination import GeoJsonPagination from .models import * from .serializers import * class LocationList(generics.ListCreateAPIView): model = Location serializer_class = LocationGeoSerializer queryset = Location.objects.all() pagination_class = PaginatedLocationGeoSerializer location_list = LocationList.as_view() class LocationDetails(generics.RetrieveUpdateDestroyAPIView): model = Location serializer_class = LocationGeoSerializer queryset = Location.objects.all() location_details = LocationDetails.as_view() class GeojsonLocationList(generics.ListCreateAPIView): model = Location serializer_class = LocationGeoFeatureSerializer queryset = Location.objects.all() pagination_class = GeoJsonPagination geojson_location_list = GeojsonLocationList.as_view() class GeojsonLocationContainedInBBoxList(generics.ListAPIView): model = Location serializer_class = LocationGeoFeatureSerializer queryset = Location.objects.all() bbox_filter_field = 'geometry' filter_backends = (InBBoxFilter,) geojson_location_contained_in_bbox_list = GeojsonLocationContainedInBBoxList.as_view() class GeojsonLocationOverlapsBBoxList(GeojsonLocationContainedInBBoxList): bbox_filter_include_overlapping = True geojson_location_overlaps_bbox_list = GeojsonLocationOverlapsBBoxList.as_view() class GeojsonLocationContainedInTileList(generics.ListAPIView): model = Location serializer_class = LocationGeoFeatureSerializer queryset = Location.objects.all() bbox_filter_field = 'geometry' filter_backends = (TMSTileFilter,) geojson_location_contained_in_tile_list = GeojsonLocationContainedInTileList.as_view() class GeojsonLocationOverlapsTileList(GeojsonLocationContainedInTileList): bbox_filter_include_overlapping = True geojson_location_overlaps_tile_list = GeojsonLocationOverlapsTileList.as_view() class GeojsonLocationWithinDistanceOfPointList(generics.ListAPIView): model = Location serializer_class = LocationGeoFeatureSerializer distance_filter_convert_meters = True queryset = Location.objects.all() distance_filter_field = 'geometry' filter_backends = (DistanceToPointFilter,) geojson_location_within_distance_of_point_list = GeojsonLocationWithinDistanceOfPointList.as_view() class GeojsonLocationWithinDegreesOfPointList(GeojsonLocationWithinDistanceOfPointList): distance_filter_convert_meters = False #Default setting geojson_location_within_degrees_of_point_list = GeojsonLocationWithinDegreesOfPointList.as_view() class GeojsonLocationDetails(generics.RetrieveUpdateDestroyAPIView): model = Location serializer_class = LocationGeoFeatureSerializer queryset = Location.objects.all() geojson_location_details = GeojsonLocationDetails.as_view() class GeojsonLocationDetailsHidden(generics.RetrieveUpdateDestroyAPIView): model = Location serializer_class = LocationGeoFeatureMethodSerializer queryset = Location.objects.all() geojson_location_details_hidden = GeojsonLocationDetailsHidden.as_view() class GeojsonLocationDetailsNone(generics.RetrieveUpdateDestroyAPIView): model = Location serializer_class = NoneGeoFeatureMethodSerializer queryset = Location.objects.all() geojson_location_details_none = GeojsonLocationDetailsNone.as_view() class GeojsonLocationSlugDetails(generics.RetrieveUpdateDestroyAPIView): model = Location lookup_field = 'slug' serializer_class = LocationGeoFeatureSlugSerializer queryset = Location.objects.all() geojson_location_slug_details = GeojsonLocationSlugDetails.as_view() class GeojsonLocationFalseIdDetails(generics.RetrieveUpdateDestroyAPIView): model = Location serializer_class = LocationGeoFeatureFalseIdSerializer queryset = Location.objects.all() geojson_location_falseid_details = GeojsonLocationFalseIdDetails.as_view() class GeojsonLocationNoIdDetails(generics.RetrieveUpdateDestroyAPIView): model = Location serializer_class = LocationGeoFeatureNoIdSerializer queryset = Location.objects.all() geojson_location_noid_details = GeojsonLocationNoIdDetails.as_view() class LocationFilter(GeoFilterSet): contains_properly = GeometryFilter(name='geometry', lookup_expr='contains_properly') class Meta: model = Location fields = ['contains_properly'] class GeojsonLocationContainedInGeometry(generics.ListAPIView): queryset = Location.objects.all() serializer_class = LocationGeoSerializer filter_class = LocationFilter filter_backends = (DjangoFilterBackend,) geojson_contained_in_geometry = GeojsonLocationContainedInGeometry.as_view() class GeojsonLocatedFileDetails(generics.RetrieveUpdateDestroyAPIView): model = LocatedFile serializer_class = LocatedFileGeoFeatureSerializer queryset = LocatedFile.objects.all() geojson_located_file_details = GeojsonLocatedFileDetails.as_view() class GeojsonBoxedLocationDetails(generics.RetrieveUpdateDestroyAPIView): model = BoxedLocation serializer_class = BoxedLocationGeoFeatureSerializer queryset = BoxedLocation.objects.all() geojson_boxedlocation_details = GeojsonBoxedLocationDetails.as_view() class GeojsonBoxedLocationList(generics.ListCreateAPIView): model = BoxedLocation serializer_class = BoxedLocationGeoFeatureSerializer queryset = BoxedLocation.objects.all() geojson_boxedlocation_list = GeojsonBoxedLocationList.as_view() class GeojsonLocationBboxList(generics.ListCreateAPIView): model = Location serializer_class = LocationGeoFeatureBboxSerializer queryset = Location.objects.all() geojson_location_bbox_list = GeojsonLocationBboxList.as_view() djangorestframework-gis-0.12/tests/django_restframework_gis_tests/test_filters.py0000664000175000017500000004417613202034134032154 0ustar nemesisnemesis00000000000000import json import urllib from unittest import skipIf from django.conf import settings from django.test import TestCase from django.contrib.gis.geos import GEOSGeometry, Polygon try: from django.urls import reverse except ImportError: from django.core.urlresolvers import reverse from .models import Location has_spatialite = settings.DATABASES['default']['ENGINE'] == 'django.contrib.gis.db.backends.spatialite' class TestRestFrameworkGisFilters(TestCase): """ unit tests for filters feature in restframework_gis """ def setUp(self): self.location_contained_in_bbox_list_url = reverse('api_geojson_location_list_contained_in_bbox_filter') self.location_overlaps_bbox_list_url = reverse('api_geojson_location_list_overlaps_bbox_filter') self.location_contained_in_tile_list_url = reverse('api_geojson_location_list_contained_in_tile_filter') self.location_overlaps_tile_list_url = reverse('api_geojson_location_list_overlaps_tile_filter') self.location_within_distance_of_point_list_url = reverse('api_geojson_location_list_within_distance_of_point_filter') self.location_within_degrees_of_point_list_url = reverse('api_geojson_location_list_within_degrees_of_point_filter') self.geojson_contained_in_geometry = reverse('api_geojson_contained_in_geometry') def test_inBBOXFilter_filtering(self): """ Checks that the inBBOXFilter returns only objects strictly contained in the bounding box given by the in_bbox URL parameter """ self.assertEqual(Location.objects.count(), 0) # Bounding box xmin = 0 ymin = 0 xmax = 10 ymax = 10 url_params = '?in_bbox=%d,%d,%d,%d&format=json' % (xmin, ymin, xmax, ymax) # Square with bottom left at (1,1), top right at (9,9) isContained = Location() isContained.name = 'isContained' isContained.geometry = Polygon(((1,1),(9,1),(9,9),(1,9),(1,1))) isContained.save() isEqualToBounds = Location() isEqualToBounds.name = 'isEqualToBounds' isEqualToBounds.geometry = Polygon(((0,0),(10,0),(10,10),(0,10),(0,0))) isEqualToBounds.save() # Rectangle with bottom left at (-1,1), top right at (5,5) overlaps = Location() overlaps.name = 'overlaps' overlaps.geometry = Polygon(((-1,1),(5,1),(5,5),(-1,5),(-1,1))) overlaps.save() # Rectangle with bottom left at (-3,-3), top right at (-1,2) nonIntersecting = Location() nonIntersecting.name = 'nonIntersecting' nonIntersecting.geometry = Polygon(((-3,-3),(-1,-3),(-1,2),(-3,2),(-3,-3))) nonIntersecting.save() # Make sure we only get back the ones strictly contained in the bounding box response = self.client.get(self.location_contained_in_bbox_list_url + url_params) self.assertEqual(len(response.data['features']), 2) for result in response.data['features']: self.assertEqual(result['properties']['name'] in ('isContained', 'isEqualToBounds'), True) # Make sure we get overlapping results for the view which allows bounding box overlaps. response = self.client.get(self.location_overlaps_bbox_list_url + url_params) self.assertEqual(len(response.data['features']), 3) for result in response.data['features']: self.assertEqual(result['properties']['name'] in ('isContained', 'isEqualToBounds', 'overlaps'), True) @skipIf(has_spatialite, 'Skipped test for spatialite backend: not accurate enough') def test_TileFilter_filtering(self): """ Checks that the TMSTileFilter returns only objects strictly contained in the bounding box given by the tile URL parameter """ self.assertEqual(Location.objects.count(), 0) # Bounding box z = 1 x = 1 y = 0 url_params = '?tile=%d/%d/%d&format=json' % (z, x, y) # Square with bottom left at (1,1), top right at (9,9) isContained = Location() isContained.name = 'isContained' isContained.geometry = Polygon(((1,1),(9,1),(9,9),(1,9),(1,1))) isContained.save() isEqualToBounds = Location() isEqualToBounds.name = 'isEqualToBounds' isEqualToBounds.geometry = Polygon(((0,0),(0,85.05113),(180,85.05113),(180,0),(0,0))) isEqualToBounds.save() # Rectangle with bottom left at (-1,1), top right at (5,5) overlaps = Location() overlaps.name = 'overlaps' overlaps.geometry = Polygon(((-1,1),(5,1),(5,5),(-1,5),(-1,1))) overlaps.save() # Rectangle with bottom left at (-3,-3), top right at (-1,2) nonIntersecting = Location() nonIntersecting.name = 'nonIntersecting' nonIntersecting.geometry = Polygon(((-3,-3),(-1,-3),(-1,2),(-3,2),(-3,-3))) nonIntersecting.save() # Make sure we only get back the ones strictly contained in the bounding box response = self.client.get(self.location_contained_in_tile_list_url + url_params) self.assertEqual(len(response.data['features']), 2) for result in response.data['features']: self.assertEqual(result['properties']['name'] in ('isContained', 'isEqualToBounds'), True) # Make sure we get overlapping results for the view which allows bounding box overlaps. response = self.client.get(self.location_overlaps_tile_list_url + url_params) self.assertEqual(len(response.data['features']), 3) for result in response.data['features']: self.assertEqual(result['properties']['name'] in ('isContained', 'isEqualToBounds', 'overlaps'), True) @skipIf(has_spatialite, 'Skipped test for spatialite backend: missing feature "dwithin"') def test_DistanceToPointFilter_filtering(self): """ Checks that the DistancFilter returns only objects within the given distance of the given geometry defined by the URL parameters """ self.assertEqual(Location.objects.count(), 0) # Filter parameters distance = 5000 # meters point_inside_ggpark = [-122.49034881591797, 37.76949349270407] point_on_golden_gate_bridge = [-122.47894, 37.8199] point_on_alcatraz = [-122.4222, 37.82667] point_on_treasure_island = [-122.3692, 37.8244] point_on_angel_island = [-122.4326, 37.86091] url_params = '?dist=%0.4f&point=hello&format=json' % (distance) response = self.client.get('%s%s' % (self.location_within_distance_of_point_list_url, url_params)) self.assertEqual(response.status_code, 400) url_params = '?dist=%0.4f&point=%0.4f,%0.4f&format=json' % (distance, point_on_alcatraz[0], point_on_alcatraz[1]) treasure_island_geojson = """{ "type": "Polygon", "coordinates": [ [ [ -122.44640350341795, 37.86103094116189 ], [ -122.44262695312501, 37.85506751416839 ], [ -122.43481636047363, 37.853305500228025 ], [ -122.42975234985352, 37.854660899304704 ], [ -122.41953849792479, 37.852627791344894 ], [ -122.41807937622069, 37.853305500228025 ], [ -122.41868019104004, 37.86211514878027 ], [ -122.42391586303711, 37.870584971740065 ], [ -122.43035316467285, 37.8723465726078 ], [ -122.43515968322752, 37.86963639998042 ], [ -122.43953704833984, 37.86882332875222 ], [ -122.44640350341795, 37.86103094116189 ] ] ] }""" treasure_island_geom = GEOSGeometry(treasure_island_geojson) treasure_island = Location() treasure_island.name = "Treasure Island" treasure_island.geometry = treasure_island_geom treasure_island.full_clean() treasure_island.save() ggpark_geojson = """{ "type": "Polygon", "coordinates": [ [ [ -122.5111198425293, 37.77125750792944 ], [ -122.51026153564452, 37.76447260365713 ], [ -122.45309829711913, 37.76677954095475 ], [ -122.45481491088867, 37.77424266859531 ], [ -122.5111198425293, 37.77125750792944 ] ] ] }""" ggpark_geom = GEOSGeometry(ggpark_geojson) ggpark = Location() ggpark.name = "Golden Gate Park" ggpark.geometry = ggpark_geom ggpark.save() # Make sure we only get back the ones within the distance response = self.client.get('%s%s' % (self.location_within_distance_of_point_list_url, url_params)) self.assertEqual(len(response.data['features']), 1) for result in response.data['features']: self.assertEqual(result['properties']['name'] in (treasure_island.name), True) # Make sure we get back all the ones within the distance distance = 7000 url_params = '?dist=%0.4f&point=%0.4f,%0.4f&format=json' % (distance, point_on_alcatraz[0], point_on_alcatraz[1]) response = self.client.get('%s%s' % (self.location_within_distance_of_point_list_url, url_params)) self.assertEqual(len(response.data['features']), 2) for result in response.data['features']: self.assertEqual(result['properties']['name'] in (ggpark.name, treasure_island.name), True) # Make sure we only get back the ones within the distance degrees = .05 url_params = '?dist=%0.4f&point=%0.4f,%0.4f&format=json' % (degrees, point_on_alcatraz[0], point_on_alcatraz[1]) response = self.client.get(self.location_within_degrees_of_point_list_url + url_params) self.assertEqual(len(response.data['features']), 1) for result in response.data['features']: self.assertEqual(result['properties']['name'] in (treasure_island.name), True) @skipIf(has_spatialite, 'Skipped test for spatialite backend: missing feature "contains_properly"') def test_GeometryField_filtering(self): """ Checks that the GeometryField allows sane filtering. """ self.assertEqual(Location.objects.count(), 0) treasure_island_geojson = """{ "type": "Polygon", "coordinates": [ [ [ -122.44640350341795, 37.86103094116189 ], [ -122.44262695312501, 37.85506751416839 ], [ -122.43481636047363, 37.853305500228025 ], [ -122.42975234985352, 37.854660899304704 ], [ -122.41953849792479, 37.852627791344894 ], [ -122.41807937622069, 37.853305500228025 ], [ -122.41868019104004, 37.86211514878027 ], [ -122.42391586303711, 37.870584971740065 ], [ -122.43035316467285, 37.8723465726078 ], [ -122.43515968322752, 37.86963639998042 ], [ -122.43953704833984, 37.86882332875222 ], [ -122.44640350341795, 37.86103094116189 ] ] ] }""" treasure_island_geom = GEOSGeometry(treasure_island_geojson) treasure_island = Location() treasure_island.name = "Treasure Island" treasure_island.geometry = treasure_island_geom treasure_island.full_clean() treasure_island.save() ggpark_geojson = """{ "type": "Polygon", "coordinates": [ [ [ -122.5111198425293, 37.77125750792944 ], [ -122.51026153564452, 37.76447260365713 ], [ -122.45309829711913, 37.76677954095475 ], [ -122.45481491088867, 37.77424266859531 ], [ -122.5111198425293, 37.77125750792944 ] ] ] }""" ggpark_geom = GEOSGeometry(ggpark_geojson) ggpark = Location() ggpark.name = "Golden Gate Park" ggpark.geometry = ggpark_geom ggpark.save() point_inside_ggpark_geojson = """{ "type": "Point", "coordinates": [ -122.49034881591797, 37.76949349270407 ] }""" try: quoted_param = urllib.quote(point_inside_ggpark_geojson) except AttributeError: quoted_param = urllib.parse.quote(point_inside_ggpark_geojson) url_params = "?contains_properly=%s" % quoted_param response = self.client.get('{0}{1}'.format(self.geojson_contained_in_geometry, url_params)) self.assertEqual(len(response.data), 1) geometry_response = GEOSGeometry(json.dumps(response.data[0]['geometry'])) self.assertTrue(geometry_response.equals_exact(ggpark_geom)) self.assertEqual(response.data[0]['name'], ggpark.name) # try without any param, should return both response = self.client.get(self.geojson_contained_in_geometry) self.assertEqual(len(response.data), 2) def test_inBBOXFilter_filtering_none(self): url_params = '?in_bbox=&format=json' response = self.client.get(self.location_contained_in_bbox_list_url + url_params) self.assertDictEqual(response.data, {'type':'FeatureCollection','features':[]}) def test_inBBOXFilter_ValueError(self): url_params = '?in_bbox=0&format=json' response = self.client.get(self.location_contained_in_bbox_list_url + url_params) self.assertEqual(response.data['detail'], 'Invalid bbox string supplied for parameter in_bbox') def test_inBBOXFilter_filter_field_none(self): from .views import GeojsonLocationContainedInBBoxList as view original_value = view.bbox_filter_field view.bbox_filter_field = None url_params = '?in_bbox=0,0,0,0&format=json' response = self.client.get(self.location_contained_in_bbox_list_url + url_params) self.assertDictEqual(response.data, {'type':'FeatureCollection','features':[]}) view.bbox_filter_field = original_value def test_TileFilter_filtering_none(self): url_params = '?tile=&format=json' response = self.client.get(self.location_contained_in_tile_list_url + url_params) self.assertEqual(response.data, {'type':'FeatureCollection','features':[]}) def test_TileFilter_ValueError(self): url_params = '?tile=1/0&format=json' response = self.client.get(self.location_contained_in_tile_list_url + url_params) self.assertEqual(response.data['detail'], 'Invalid tile string supplied for parameter tile') def test_DistanceToPointFilter_filtering_none(self): url_params = '?dist=%0.4f&point=&format=json' % 5000 response = self.client.get('%s%s' % (self.location_within_distance_of_point_list_url, url_params)) self.assertDictEqual(response.data, {'type':'FeatureCollection','features':[]}) def test_DistanceToPointFilter_filter_field_none(self): from .views import GeojsonLocationWithinDistanceOfPointList as view original_value = view.distance_filter_field view.distance_filter_field = None url_params = '?dist=%0.4f&point=&format=json' % 5000 response = self.client.get('%s%s' % (self.location_within_distance_of_point_list_url, url_params)) self.assertDictEqual(response.data, {'type':'FeatureCollection','features':[]}) view.distance_filter_field = original_value def test_DistanceToPointFilter_ValueError_point(self): url_params = '?dist=500.0&point=hello&format=json' response = self.client.get('%s%s' % (self.location_within_distance_of_point_list_url, url_params)) self.assertEqual(response.data['detail'], 'Invalid geometry string supplied for parameter point') def test_DistanceToPointFilter_ValueError_distance(self): url_params = '?dist=wrong&point=12.0,42.0&format=json' response = self.client.get('%s%s' % (self.location_within_distance_of_point_list_url, url_params)) self.assertEqual(response.data['detail'], 'Invalid distance string supplied for parameter dist') djangorestframework-gis-0.12/tests/django_restframework_gis_tests/test_bbox.py0000664000175000017500000000707713202034134031435 0ustar nemesisnemesis00000000000000import json from django.test import TestCase try: from django.urls import reverse except ImportError: from django.core.urlresolvers import reverse from django.core.exceptions import ImproperlyConfigured from rest_framework_gis import serializers as gis_serializers from .models import BoxedLocation, Location from .serializers import LocationGeoSerializer class TestRestFrameworkGisBBox(TestCase): """ unit tests for bbox support in restframework_gis """ def setUp(self): self.geojson_boxedlocation_list_url = reverse('api_geojson_boxedlocation_list') self.geojson_location_bbox_list_url = reverse('api_geojson_location_bbox_list') def _create_locations(self): self.bl1 = BoxedLocation.objects.create(id=1, name='l1', slug='l1', geometry='POINT (13.007 42.423)', bbox_geometry='POLYGON((12.997 42.413,12.997 42.433,13.017 42.433,13.017 42.413,12.997 42.413))') self.bl2 = BoxedLocation.objects.create(id=2, name='l2', slug='l2', geometry='POINT (12.007 43.423)', bbox_geometry='POLYGON((11.997 43.413,11.997 43.433,12.017 43.433,12.017 43.413,11.997 43.413))') self.l1 = Location.objects.create(id=1, name='l1', slug='l1', geometry='POLYGON((12.997 42.413,12.997 42.433,13.017 42.433,13.017 42.413,12.997 42.413))') def test_list(self): self._create_locations() response = self.client.get(self.geojson_boxedlocation_list_url) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data['features']), 2) for feature in response.data['features']: self.assertIn('bbox', feature) fid = feature['id'] if fid==1: self.assertEqual(feature['bbox'], self.bl1.bbox_geometry.extent) elif fid==2: self.assertEqual(feature['bbox'], self.bl2.bbox_geometry.extent) else: self.fail("Unexpected id: {0}".format(fid)) BoxedLocation.objects.all().delete() def test_post_location_list_geojson(self): self.assertEqual(BoxedLocation.objects.count(), 0) data = { "properties": { "name": "geojson input test", }, "geometry": { "type": "Point", "coordinates": [ 12.49, 41.89 ] }, "bbox": [11.0, 40.0, 13.0, 42.0] } response = self.client.post(self.geojson_boxedlocation_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.status_code, 201) self.assertEqual(BoxedLocation.objects.count(), 1) self.assertEqual(BoxedLocation.objects.all()[0].bbox_geometry.extent, (11.0,40.0,13.0,42.0)) def test_get_autogenerated_location_bbox_geojson(self): self._create_locations() response = self.client.get(self.geojson_location_bbox_list_url) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.data['features']), 1) self.assertEqual(response.data['features'][0]['bbox'], self.l1.geometry.extent) def test_bbox_improperly_configured(self): self._create_locations() class LocationGeoFeatureSerializer(gis_serializers.GeoFeatureModelSerializer): class Meta: model = Location geo_field = 'geometry' bbox_geo_field = 'geometry' auto_bbox = True with self.assertRaises(ImproperlyConfigured): LocationGeoFeatureSerializer(instance=self.l1) djangorestframework-gis-0.12/tests/django_restframework_gis_tests/models.py0000664000175000017500000000214413202034134030715 0ustar nemesisnemesis00000000000000from django.contrib.gis.db import models from django.utils.text import slugify __all__ = [ 'Location', 'LocatedFile', 'BoxedLocation' ] class BaseModel(models.Model): name = models.CharField(max_length=32) slug = models.SlugField(max_length=128, unique=True, blank=True) timestamp = models.DateTimeField(null=True, blank=True) geometry = models.GeometryField() class Meta: abstract = True def __unicode__(self): return self.name def _generate_slug(self): if self.slug == '' or self.slug is None: try: name = unicode(self.name) except NameError: name = self.name self.slug = slugify(name) def clean(self): self._generate_slug() def save(self, *args, **kwargs): self._generate_slug() super(BaseModel, self).save(*args, **kwargs) class Location(BaseModel): pass class LocatedFile(BaseModel): file = models.FileField(upload_to='located_files', blank=True, null=True) class BoxedLocation(BaseModel): bbox_geometry = models.PolygonField() djangorestframework-gis-0.12/tests/django_restframework_gis_tests/admin.py0000664000175000017500000000067012544034142030533 0ustar nemesisnemesis00000000000000from django.contrib import admin from django.conf import settings GEODJANGO_IMPROVED_WIDGETS = 'olwidget' in settings.INSTALLED_APPS if GEODJANGO_IMPROVED_WIDGETS: from olwidget.admin import GeoModelAdmin else: from django.contrib.gis.admin import ModelAdmin as GeoModelAdmin from .models import Location class LocationAdmin(GeoModelAdmin): list_display = ('name', 'geometry') admin.site.register(Location, LocationAdmin)djangorestframework-gis-0.12/tests/django_restframework_gis_tests/tests.py0000664000175000017500000006755713202034134030617 0ustar nemesisnemesis00000000000000""" unit tests for restframework_gis """ import urllib import sys import json import pickle from django.test import TestCase from django.contrib.gis.geos import GEOSGeometry, Polygon, Point try: from django.urls import reverse except ImportError: from django.core.urlresolvers import reverse from django.core.exceptions import ImproperlyConfigured from rest_framework_gis import serializers as gis_serializers from rest_framework_gis.fields import GeoJsonDict from .models import Location, LocatedFile from .serializers import LocationGeoSerializer class TestRestFrameworkGis(TestCase): def setUp(self): self.location_list_url = reverse('api_location_list') self.geojson_location_list_url = reverse('api_geojson_location_list') self.geos_error_message = 'Invalid format: string or unicode input unrecognized as GeoJSON, WKT EWKT or HEXEWKB.' def _create_locations(self): self.l1 = Location.objects.create(id=1, name='l1', slug='l1', geometry='POINT (13.0078125000020002 42.4234565179379999)') self.l2 = Location.objects.create(id=2, name='l2', slug='l2', geometry='POINT (12.0078125000020002 43.4234565179379999)') def test_get_location_list(self): response = self.client.get(self.location_list_url) self.assertEqual(response.status_code, 200) def test_post_location_list_geojson(self): self.assertEqual(Location.objects.count(), 0) data = { "name": "geojson input test", "geometry": { "type": "GeometryCollection", "geometries": [ { "type": "Point", "coordinates": [ 12.492324113849, 41.890307434153 ] } ] } } response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 1) data = { "name": "geojson input test2", "geometry": { "type": "Point", "coordinates": [ 12.492324113849, 41.890307434153 ] } } response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 2) def test_post_location_list_geojson_as_multipartformdata(self): """ emulate sending geojson string in webform """ self.assertEqual(Location.objects.count(), 0) data = { "name": "geojson input test", "geometry": json.dumps({ "type": "GeometryCollection", "geometries": [ { "type": "Point", "coordinates": [ 12.492324113849, 41.890307434153 ] } ] }) } response = self.client.post(self.location_list_url, data) self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 1) def test_post_HTML_browsable_api(self): self.assertEqual(Location.objects.count(), 0) data = { "name": "geojson input test2", "slug": "prova", "geometry": json.dumps({ "type": "GeometryCollection", "geometries": [ { "type": "Point", "coordinates": [ 12.492324113849, 41.890307434153 ] } ] }) } response = self.client.post(self.location_list_url, data, HTTP_ACCEPT='text/html') self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 1) location = Location.objects.all()[0] self.assertEqual(location.name, 'geojson input test2') self.assertEqual(location.slug, 'prova') def test_post_location_list_WKT(self): self.assertEqual(Location.objects.count(), 0) data = { 'name': 'WKT input test', 'geometry': 'POINT (12.492324113849 41.890307434153)' } response = self.client.post(self.location_list_url, data) self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 1) def test_post_location_list_EWKT(self): self.assertEqual(Location.objects.count(), 0) data = { 'name': 'EWKT input test', 'geometry': 'SRID=28992;POINT(221160 600204)' } response = self.client.post(self.location_list_url, data) expected_coords = (6.381495826183805, 53.384066927384985) self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 1) for l, e in zip(Location.objects.get(name='EWKT input test').geometry.coords, expected_coords): self.assertAlmostEqual(l, e, places=5) def test_post_location_list_WKT_as_json(self): self.assertEqual(Location.objects.count(), 0) data = { 'name': 'WKT input test', 'geometry': 'POINT (12.492324113849 41.890307434153)' } response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 1) def test_post_location_list_empty_geometry(self): data = { 'name': 'empty input test' } response = self.client.post(self.location_list_url, data) self.assertEqual(response.data['geometry'][0], 'This field is required.') data = { 'name': 'empty input test', 'geometry': '' } response = self.client.post(self.location_list_url, data) self.assertEqual(response.data['geometry'][0], 'This field is required.') data = { 'name': 'empty input test' } response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.data['geometry'][0], 'This field is required.') data = { 'name': 'empty input test', 'geometry': '' } response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.data['geometry'][0], 'This field is required.') def test_post_location_list_invalid_WKT(self): data = { 'name': 'WKT wrong input test', 'geometry': 'I AM OBVIOUSLY WRONG' } response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.status_code, 400) self.assertEqual(Location.objects.count(), 0) self.assertEqual(response.data['geometry'][0], self.geos_error_message) # repeat as multipart form data response = self.client.post(self.location_list_url, data) self.assertEqual(response.data['geometry'][0], self.geos_error_message) data = { 'name': 'I AM MODERATELY WRONG', 'geometry': 'POINT (12.492324113849, 41.890307434153)' } response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.data['geometry'][0], self.geos_error_message) # repeat as multipart form data response = self.client.post(self.location_list_url, data) self.assertEqual(response.data['geometry'][0], self.geos_error_message) def test_post_location_list_invalid_geojson(self): data = { "name": "quite wrong", "geometry": { "type": "ARRRR", "dasdas": [ { "STtype": "PTUAMAoint", "NNAare":"rgon" } ] } } response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.data['geometry'][0], self.geos_error_message) data = { "name": "very wrong", "geometry": ['a', 'b', 'c'] } response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.data['geometry'][0], self.geos_error_message) data = { "name": "very wrong", "geometry": False } response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.data['geometry'][0], self.geos_error_message) data = { "name": "very wrong", "geometry": { "value": { "nested": ["yo"] } } } response = self.client.post(self.location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.data['geometry'][0], self.geos_error_message) def test_geojson_format(self): """ test geojson format """ location = Location.objects.create(name='geojson test', geometry='POINT (135.0 45.0)') url = reverse('api_geojson_location_details', args=[location.id]) expected = { 'id': location.id, 'type': 'Feature', 'properties': { 'details': "http://testserver/geojson/%s/" % location.id, 'name': 'geojson test', 'fancy_name': 'Kool geojson test', 'timestamp': None, 'slug': 'geojson-test', }, 'geometry': { 'type': 'Point', 'coordinates': [ 135.0, 45.0, ], } } response = self.client.get(url) if sys.version_info>(3,0,0): self.assertCountEqual(json.dumps(response.data), json.dumps(expected)) else: self.assertItemsEqual(json.dumps(response.data), json.dumps(expected)) response = self.client.get(url, HTTP_ACCEPT='text/html') self.assertContains(response, "Kool geojson test") def test_geojson_id_attribute(self): location = Location.objects.create(name='geojson test', geometry='POINT (10.1 10.1)') url = reverse('api_geojson_location_details', args=[location.id]) response = self.client.get(url) self.assertEqual(response.data['id'], location.id) def test_geojson_id_attribute_slug(self): location = Location.objects.create(name='geojson test', geometry='POINT (10.1 10.1)') url = reverse('api_geojson_location_slug_details', args=[location.slug]) response = self.client.get(url) self.assertEqual(response.data['id'], location.slug) def test_geojson_false_id_attribute_slug(self): location = Location.objects.create(name='falseid test', geometry='POINT (10.1 10.1)') url = reverse('api_geojson_location_falseid_details', args=[location.id]) response = self.client.get(url) self.assertEqual(response.data['properties']['name'], 'falseid test') with self.assertRaises(KeyError): response.data['id'] def test_geojson_no_id_attribute_slug(self): location = Location.objects.create(name='noid test', geometry='POINT (10.1 10.1)') url = reverse('api_geojson_location_noid_details', args=[location.id]) response = self.client.get(url) self.assertEqual(response.data['properties']['name'], 'noid test') with self.assertRaises(KeyError): response.data['id'] def test_geojson_filefield_attribute(self): located_file = LocatedFile.objects.create(name='geojson filefield test', geometry='POINT (10.1 10.1)') url = reverse('api_geojson_located_file_details', args=[located_file.id]) response = self.client.get(url) self.assertEqual(response.data['properties']['file'], None) def test_post_geojson_location_list(self): self.assertEqual(Location.objects.count(), 0) data = { "type": "Feature", "properties": { "name": "point?", "details": "ignore this" }, "geometry": { "type": "Point", "coordinates": [ 10.1, 10.1 ] } } response = self.client.post(self.geojson_location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 1) url = reverse('api_geojson_location_details', args=[Location.objects.order_by('-id')[0].id]) response = self.client.get(url) self.assertEqual(response.data['properties']['name'], "point?") self.assertEqual(response.data['geometry']['type'], "Point") self.assertEqual(json.dumps(response.data['geometry']['coordinates']), "[10.1, 10.1]") self.assertNotEqual(response.data['properties']['details'], "ignore this") def test_post_geojson_location_list_HTML(self): self.assertEqual(Location.objects.count(), 0) data = { "type": "Feature", "properties": { "name": "point?", "details": "ignore this" }, "geometry": { "type": "Point", "coordinates": [ 10.1, 10.1 ] } } response = self.client.post(self.geojson_location_list_url, data=json.dumps(data), content_type='application/json', HTTP_ACCEPT='text/html') self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 1) url = reverse('api_geojson_location_details', args=[Location.objects.order_by('-id')[0].id]) response = self.client.get(url) self.assertEqual(response.data['properties']['name'], "point?") self.assertEqual(response.data['geometry']['type'], "Point") self.assertEqual(json.dumps(response.data['geometry']['coordinates']), "[10.1, 10.1]") self.assertNotEqual(response.data['properties']['details'], "ignore this") def test_post_invalid_geojson_location_list(self): data = { "type": "Feature", "properties": { "details": "ignore this" }, "geometry": { "type": "Point", "coordinates": [ 10.1, 10.1 ] } } response = self.client.post(self.geojson_location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.status_code, 400) self.assertEqual(Location.objects.count(), 0) self.assertEqual(response.data['name'][0], "This field is required.") data = { "type": "Feature", "properties": { "name": "point?", }, "geometry": { "type": "Point", "WRONG": {} } } response = self.client.post(self.geojson_location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.status_code, 400) self.assertEqual(Location.objects.count(), 0) self.assertEqual(response.data['geometry'][0], self.geos_error_message) def test_post_geojson_location_list_WKT(self): self.assertEqual(Location.objects.count(), 0) data = { "type": "Feature", "properties": { "name": "point?", }, "geometry": "POINT (10.1 10.1)" } response = self.client.post(self.geojson_location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 1) url = reverse('api_geojson_location_details', args=[Location.objects.order_by('-id')[0].id]) response = self.client.get(url) self.assertEqual(response.data['properties']['name'], "point?") self.assertEqual(response.data['geometry']['type'], "Point") self.assertEqual(json.dumps(response.data['geometry']['coordinates']), "[10.1, 10.1]") def test_geofeatured_model_serializer_compatible_with_geomodel_serializer(self): self.assertEqual(Location.objects.count(), 0) data = { "name": "geojson input test", "geometry": { "type": "GeometryCollection", "geometries": [ { "type": "Point", "coordinates": [ 12.492324113849, 41.890307434153 ] } ] } } response = self.client.post(self.geojson_location_list_url, data=json.dumps(data), content_type='application/json') self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 1) def test_geofeatured_model_post_as_multipartformdata(self): """ emulate sending geojson string in webform """ self.assertEqual(Location.objects.count(), 0) data = { "name": "geojson input test", "geometry": json.dumps({ "type": "Point", "coordinates": [ 12.492324113849, 41.890307434153 ] }) } response = self.client.post(self.location_list_url, data) self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 1) self.assertEqual(response.data['geometry']['type'], "Point") def test_HTML_browsable_geojson_location_list(self): response = self.client.get(self.geojson_location_list_url, HTTP_ACCEPT='text/html') self.assertEqual(response.status_code, 200) self._create_locations() response = self.client.get(self.geojson_location_list_url, HTTP_ACCEPT='text/html') self.assertContains(response, 'l1') self.assertContains(response, 'l2') def test_post_geojson_location_list_HTML_web_form(self): self.assertEqual(Location.objects.count(), 0) data = { "name": "HTML test", "geometry": json.dumps({ "type": "Point", "coordinates": [ 10.1, 10.1 ] }) } response = self.client.post(self.geojson_location_list_url, data, HTTP_ACCEPT='text/html') self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 1) location = Location.objects.all()[0] self.assertEqual(location.name, "HTML test") self.assertEqual(location.geometry.geom_type, "Point") def test_post_geojson_location_list_HTML_web_form_WKT(self): self.assertEqual(Location.objects.count(), 0) data = { "name": "HTML test WKT", "geometry": "POINT (10.1 10.1)" } response = self.client.post(self.geojson_location_list_url, data, HTTP_ACCEPT='text/html') self.assertEqual(response.status_code, 201) self.assertEqual(Location.objects.count(), 1) location = Location.objects.all()[0] self.assertEqual(location.name, "HTML test WKT") def test_geojson_HTML_widget_value(self): self._create_locations() response = self.client.get(self.geojson_location_list_url, HTTP_ACCEPT='text/html') self.assertContains(response, '