diff --git a/README.rst b/README.rst index 66b45310..7f8a32eb 100644 --- a/README.rst +++ b/README.rst @@ -250,6 +250,9 @@ to be serialized as the "geometry". For example: # as with a ModelSerializer. fields = ('id', 'address', 'city', 'state') +If your model is geometry-less, you can set ``geo_field`` to ``None`` +and a null geometry will be produced. + Using GeometrySerializerMethodField as "geo_field" ################################################## diff --git a/rest_framework_gis/fields.py b/rest_framework_gis/fields.py index 395ef508..d22a7e78 100644 --- a/rest_framework_gis/fields.py +++ b/rest_framework_gis/fields.py @@ -65,7 +65,7 @@ def to_internal_value(self, value): value = json.dumps(value) try: return GEOSGeometry(value) - except (GEOSException): + except GEOSException: raise ValidationError( _( 'Invalid format: string or unicode input unrecognized as GeoJSON, WKT EWKT or HEXEWKB.' diff --git a/rest_framework_gis/serializers.py b/rest_framework_gis/serializers.py index 60ac9384..4b270fb0 100644 --- a/rest_framework_gis/serializers.py +++ b/rest_framework_gis/serializers.py @@ -72,8 +72,11 @@ def __init__(self, *args, **kwargs): 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'.") + if not hasattr(meta, 'geo_field'): + raise ImproperlyConfigured( + "You must define a 'geo_field'. " + "Set it to None if there is no geometry." + ) def check_excludes(field_name, field_role): """make sure the field is not excluded""" @@ -128,12 +131,15 @@ def to_representation(self, instance): # 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) - processed_fields.add(self.Meta.geo_field) + # geometry attribute + # must be present in output according to GeoJSON spec + if self.Meta.geo_field: + field = self.fields[self.Meta.geo_field] + geo_value = field.get_attribute(instance) + feature["geometry"] = field.to_representation(geo_value) + processed_fields.add(self.Meta.geo_field) + else: + feature["geometry"] = None # Bounding Box # if auto_bbox feature is enabled @@ -211,7 +217,7 @@ def unformat_geojson(self, feature): """ attrs = feature["properties"] - if 'geometry' in feature: + if 'geometry' in feature and self.Meta.geo_field: attrs[self.Meta.geo_field] = feature['geometry'] if self.Meta.id_field and 'id' in feature: diff --git a/tests/django_restframework_gis_tests/migrations/0001_initial.py b/tests/django_restframework_gis_tests/migrations/0001_initial.py index 7458e470..f33b4b0c 100644 --- a/tests/django_restframework_gis_tests/migrations/0001_initial.py +++ b/tests/django_restframework_gis_tests/migrations/0001_initial.py @@ -7,7 +7,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [] diff --git a/tests/django_restframework_gis_tests/migrations/0002_nullable.py b/tests/django_restframework_gis_tests/migrations/0002_nullable.py index 5f0d7530..06059ef5 100644 --- a/tests/django_restframework_gis_tests/migrations/0002_nullable.py +++ b/tests/django_restframework_gis_tests/migrations/0002_nullable.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ('django_restframework_gis_tests', '0001_initial'), ] diff --git a/tests/django_restframework_gis_tests/models.py b/tests/django_restframework_gis_tests/models.py index 3c8713d0..cf6adf4a 100644 --- a/tests/django_restframework_gis_tests/models.py +++ b/tests/django_restframework_gis_tests/models.py @@ -20,7 +20,6 @@ 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 @@ -45,14 +44,14 @@ def save(self, *args, **kwargs): class Location(BaseModel): - pass + geometry = models.GeometryField() -class LocatedFile(BaseModel): +class LocatedFile(Location): file = models.FileField(upload_to='located_files', blank=True, null=True) -class BoxedLocation(BaseModel): +class BoxedLocation(Location): bbox_geometry = models.PolygonField() diff --git a/tests/django_restframework_gis_tests/serializers.py b/tests/django_restframework_gis_tests/serializers.py index 95ef3371..9c408216 100644 --- a/tests/django_restframework_gis_tests/serializers.py +++ b/tests/django_restframework_gis_tests/serializers.py @@ -185,6 +185,13 @@ class Meta: fields = ['name', 'slug', 'id'] +class NoGeoFeatureMethodSerializer(gis_serializers.GeoFeatureModelSerializer): + class Meta: + model = Location + geo_field = None + fields = ['name', 'slug', 'id'] + + class PointSerializer(gis_serializers.GeoFeatureModelSerializer): class Meta: model = PointModel diff --git a/tests/django_restframework_gis_tests/tests.py b/tests/django_restframework_gis_tests/tests.py index d4305800..013abb70 100644 --- a/tests/django_restframework_gis_tests/tests.py +++ b/tests/django_restframework_gis_tests/tests.py @@ -634,6 +634,16 @@ def test_geometry_serializer_method_field_none(self): self.assertEqual(response.data['properties']['name'], 'None value') self.assertEqual(response.data['geometry'], None) + def test_geometry_serializer_method_field_nogeo(self): + location = Location.objects.create(name='No geometry value') + location_loaded = Location.objects.get(pk=location.id) + self.assertEqual(location_loaded.name, "No geometry value") + url = reverse('api_geojson_location_details_nogeo', args=[location.id]) + response = self.client.generic('GET', url, content_type='application/json') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data['properties']['name'], 'No geometry value') + self.assertEqual(response.data['geometry'], None) + def test_nullable_empty_geometry(self): empty = Nullable(name='empty', geometry='POINT EMPTY') empty.full_clean() diff --git a/tests/django_restframework_gis_tests/views.py b/tests/django_restframework_gis_tests/views.py index bd290338..1a289e25 100644 --- a/tests/django_restframework_gis_tests/views.py +++ b/tests/django_restframework_gis_tests/views.py @@ -23,6 +23,7 @@ LocationGeoFeatureSlugSerializer, LocationGeoFeatureWritableIdSerializer, LocationGeoSerializer, + NoGeoFeatureMethodSerializer, NoneGeoFeatureMethodSerializer, PaginatedLocationGeoSerializer, PolygonModelSerializer, @@ -167,6 +168,15 @@ class GeojsonLocationDetailsNone(generics.RetrieveUpdateDestroyAPIView): geojson_location_details_none = GeojsonLocationDetailsNone.as_view() +class GeojsonLocationDetailsNoGeo(generics.RetrieveUpdateDestroyAPIView): + model = Location + serializer_class = NoGeoFeatureMethodSerializer + queryset = Location.objects.all() + + +geojson_location_details_nogeo = GeojsonLocationDetailsNoGeo.as_view() + + class GeojsonLocationSlugDetails(generics.RetrieveUpdateDestroyAPIView): model = Location lookup_field = 'slug'