Skip to content

Commit

Permalink
Merge pull request #30 from City-of-Turku/develop
Browse files Browse the repository at this point in the history
Release v1.6
  • Loading branch information
SanttuA authored Apr 22, 2021
2 parents 14769e2 + 4a3f47b commit 45c0e8e
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 74 deletions.
2 changes: 1 addition & 1 deletion democracy/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ class Media:
"fields": ("published", "open_at", "close_at", "force_closed")
}),
(_("Area"), {
"fields": ("geojson",)
"fields": ("geometry",)
}),
(_("Contact info"), {
"fields": ("contact_persons",)
Expand Down
42 changes: 42 additions & 0 deletions democracy/migrations/0054_hearing_geometry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Generated by Django 2.2.12 on 2021-02-15 11:38

import django.contrib.gis.db.models.fields
import django.core.files.storage
from django.db import migrations, models
from django.contrib.gis.geos import GEOSGeometry, GeometryCollection
import json


def get_geometry_from_geojson(geojson):
gc = GeometryCollection()
if geojson is None:
return None

geometry_data = geojson.get('geometry', None) or geojson
geometry = GEOSGeometry(json.dumps(geometry_data))
gc.append(geometry)

return gc


def hearings_geometry_migration(apps, schema_editor):
Hearing = apps.get_model('democracy', 'Hearing')
for hearing in Hearing.objects.filter(geojson__isnull=False):
hearing.geometry = get_geometry_from_geojson(hearing.geojson)
hearing.save()


class Migration(migrations.Migration):

dependencies = [
('democracy', '0053_auto_20200813_1112'),
]

operations = [
migrations.RunPython(hearings_geometry_migration),
migrations.AlterField(
model_name='hearing',
name='geometry',
field=django.contrib.gis.db.models.fields.GeometryCollectionField(blank=True, null=True, srid=4326, verbose_name='area geometry'),
),
]
2 changes: 1 addition & 1 deletion democracy/models/hearing.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class Hearing(StringIdBaseModel, TranslatableModel):
)
servicemap_url = models.CharField(verbose_name=_('service map URL'), default='', max_length=255, blank=True)
geojson = GeoJSONField(blank=True, null=True, verbose_name=_('area'))
geometry = models.GeometryField(blank=True, null=True, verbose_name=_('area geometry'))
geometry = models.GeometryCollectionField(blank=True, null=True, verbose_name=_('area geometry'))
organization = models.ForeignKey(
Organization,
verbose_name=_('organization'),
Expand Down
20 changes: 0 additions & 20 deletions democracy/tests/test_comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -1050,26 +1050,6 @@ def test_root_endpoint_timestamp_filters(api_client, default_hearing):
assert len(response_data['results']) == comment_count - 1


@pytest.mark.django_db
def test_root_endpoint_bbox_filtering(api_client, default_hearing, geojson_feature, bbox_containing_feature, bbox_containing_geometries):
url = '/v1/comment/'
section = default_hearing.sections.first()
comment = section.comments.first()
comment.geojson = geojson_feature
comment.save()

containing_query = '?bbox=%s' % bbox_containing_feature
not_containing_query = '?bbox=%s' % bbox_containing_geometries

response = api_client.get(url + containing_query)
response_data = get_data_from_response(response)
assert len(response_data['results']) == 1

response = api_client.get(url + not_containing_query)
response_data = get_data_from_response(response)
assert len(response_data['results']) == 0


@pytest.mark.parametrize('hearing_update', [
('deleted', True),
('published', False),
Expand Down
163 changes: 118 additions & 45 deletions democracy/tests/test_hearing.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,27 @@ def create_hearings(n, organization=None):
)
return hearings

def create_hearings_created_by(n, organization=None, user=None):
'''
Existing hearings are not deleted as this function is used multiple
times in a specific test with multiple users/organzations.
'''
hearings = []

# Depending on the database backend, created_at dates (which are used for ordering)
# may be truncated to the closest second, so we purposefully backdate these
# to ensure ordering on all platforms.
for i in range(n):
hearings.append(
Hearing.objects.create(
title='Test purpose created hearing title %s' % (i + 1),
created_at=now() - datetime.timedelta(seconds=1 + (n - i)),
organization=organization,
created_by_id=user.id
)
)
return hearings


@pytest.mark.django_db
def test_list_all_hearings_no_objects(api_client):
Expand Down Expand Up @@ -324,6 +345,81 @@ def test_filter_hearings_by_title(api_client):
assert len(data['results']) == 1
assert data['results'][0]['title'][default_lang_code] == hearings[0].title

@pytest.mark.django_db
def test_filter_hearings_created_by_me(api_client, john_smith_api_client, jane_doe_api_client, stark_doe_api_client):
# Retrieves hearings that are created by the user
main_organization = Organization.objects.create(name='main organization')

second_organization = Organization.objects.create(name='second organization')

# John is a member of the main organization
john_smith_api_client.user.admin_organizations.add(main_organization)
# Jane is a member of the main organization
jane_doe_api_client.user.admin_organizations.add(main_organization)
# Stark is a member of the second organization
stark_doe_api_client.user.admin_organizations.add(second_organization)

'''Create hearings'''
# Jane creates 6 hearings
jane_hearings = create_hearings_created_by(6,main_organization, jane_doe_api_client.user)
# John creates 3 hearings
john_hearings = create_hearings_created_by(3,main_organization, john_smith_api_client.user)
# Stark creates 1 hearing
stark_hearings = create_hearings_created_by(1,second_organization, stark_doe_api_client.user)


'''Filtering with me'''
# Jane should get 6 results when filtering with 'me'
jane_response = jane_doe_api_client.get(list_endpoint, data={"created_by": "me"})
jane_data = get_data_from_response(jane_response)
assert len(jane_data['results']) == 6

# John should get 3 results when filtering with 'me'
john_response = john_smith_api_client.get(list_endpoint, data={"created_by": "me"})
john_data = get_data_from_response(john_response)
assert len(john_data['results']) == 3

# Stark should get 1 result when filtering with 'me'
stark_response = stark_doe_api_client.get(list_endpoint, data={"created_by": "me"})
stark_data = get_data_from_response(stark_response)
assert len(stark_data['results']) == 1


'''Filtering with main_organization.name'''
# Jane should get 9 results when filtering with main_organization id
jane_response = jane_doe_api_client.get(list_endpoint, data={"created_by": main_organization.name})
jane_data = get_data_from_response(jane_response)
assert len(jane_data['results']) == 9

# John should get 9 results when filtering with main_organization id
john_response = john_smith_api_client.get(list_endpoint, data={"created_by": main_organization.name})
john_data = get_data_from_response(john_response)
assert len(john_data['results']) == 9

# Stark should get 9 results when filtering with main_organization id
stark_response = stark_doe_api_client.get(list_endpoint, data={"created_by": main_organization.name})
stark_data = get_data_from_response(stark_response)
assert len(stark_data['results']) == 9


'''Filtering with second_organization.name'''
# Jane should get 1 result when filtering with second_organization id
jane_response = jane_doe_api_client.get(list_endpoint, data={"created_by": second_organization.name})
jane_data = get_data_from_response(jane_response)
assert len(jane_data['results']) == 1

# John should get 1 result when filtering with second_organization id
john_response = john_smith_api_client.get(list_endpoint, data={"created_by": second_organization.name})
john_data = get_data_from_response(john_response)
assert len(john_data['results']) == 1

# Stark should get 1 result when filtering with second_organization id
stark_response = stark_doe_api_client.get(list_endpoint, data={"created_by": second_organization.name})
stark_data = get_data_from_response(stark_response)
assert len(stark_data['results']) == 1




@pytest.mark.parametrize('plugin_fullscreen', [
True,
Expand Down Expand Up @@ -618,7 +714,7 @@ def test_hearing_geojson_feature(request, john_smith_api_client, valid_hearing_j
hearing_geometry = json.loads(hearing.geometry.geojson)
assert hearing.geojson == feature
assert hearing_data['geojson'] == feature
assert hearing_data['geojson']['geometry'] == hearing_geometry
assert hearing_data['geojson']['geometry'] == hearing_geometry['geometries'][0]
geojson_data = get_data_from_response(john_smith_api_client.get(get_detail_url(hearing.pk), {'format': 'geojson'}))
assert geojson_data['id'] == hearing.pk
assert_common_keys_equal(geojson_data['geometry'], feature['geometry'])
Expand Down Expand Up @@ -646,19 +742,38 @@ def test_hearing_geojson_geometry_only(request, john_smith_api_client, valid_hea
hearing_geometry = json.loads(hearing.geometry.geojson)
assert hearing.geojson == geojson_geometry
assert hearing_data['geojson'] == geojson_geometry
assert hearing_data['geojson'] == hearing_geometry
assert hearing_data['geojson'] == hearing_geometry['geometries'][0]
geojson_data = get_data_from_response(john_smith_api_client.get(get_detail_url(hearing.pk), {'format': 'geojson'}))
assert geojson_data['id'] == hearing.pk
assert_common_keys_equal(geojson_data['geometry'], geojson_geometry)
assert_common_keys_equal(geojson_data['properties'], hearing_data)
map_data = get_data_from_response(john_smith_api_client.get(list_endpoint + 'map/'))
assert map_data['results'][0]['geojson'] == geojson_geometry

@pytest.mark.django_db
@pytest.mark.parametrize('geometry_fixture_name', [
'geojson_featurecollection',
])
def test_hearing_geojson_featurecollection_only(request, john_smith_api_client, valid_hearing_json, geometry_fixture_name):
geojson_geometry = request.getfixturevalue(geometry_fixture_name)
valid_hearing_json['geojson'] = geojson_geometry
response = john_smith_api_client.post(endpoint, data=valid_hearing_json, format='json')
hearing_data = get_data_from_response(response, status_code=201)
hearing = Hearing.objects.get(pk=hearing_data['id'])
hearing_geometry = json.loads(hearing.geometry.geojson)
assert hearing.geojson == geojson_geometry
assert hearing_data['geojson'] == geojson_geometry
assert hearing_data['geojson']['features'][0]['geometry'] == hearing_geometry['geometries'][0]
geojson_data = get_data_from_response(john_smith_api_client.get(get_detail_url(hearing.pk), {'format': 'geojson'}))
assert geojson_data['id'] == hearing.pk
assert_common_keys_equal(geojson_data, geojson_geometry)
assert_common_keys_equal(geojson_data['properties'], hearing_data)
map_data = get_data_from_response(john_smith_api_client.get(list_endpoint + 'map/'))
assert map_data['results'][0]['geojson'] == geojson_geometry

@pytest.mark.django_db
@pytest.mark.parametrize('geojson_fixture_name', [
'geojson_geometrycollection',
'geojson_featurecollection',
])
def test_hearing_geojson_unsupported_types(request, john_smith_api_client, valid_hearing_json, geojson_fixture_name):
geojson = request.getfixturevalue(geojson_fixture_name)
Expand All @@ -668,48 +783,6 @@ def test_hearing_geojson_unsupported_types(request, john_smith_api_client, valid
assert data['geojson'][0].startswith('Invalid geojson format. Type is not supported.')


@pytest.mark.django_db
def test_hearing_bbox_filtering(
request, api_client, random_hearing, geojson_feature,
bbox_containing_feature, bbox_containing_geometries, bbox_all):
random_hearing.geojson = geojson_feature
random_hearing.save()
containing_query = '?bbox=%s' % bbox_containing_feature
not_containing_query = '?bbox=%s' % bbox_containing_geometries
bbox_all_query = '?bbox=%s' % bbox_all
data = get_data_from_response(api_client.get(list_endpoint + containing_query))
assert len(data['results']) == 1
assert data['results'][0]['id'] == random_hearing.pk
data = get_data_from_response(api_client.get(list_endpoint + not_containing_query))
assert len(data['results']) == 0
data = get_data_from_response(api_client.get(list_endpoint + bbox_all_query))
assert len(data['results']) == 1
assert data['results'][0]['id'] == random_hearing.pk


@pytest.mark.django_db
@pytest.mark.parametrize('geometry_fixture_name', [
'geojson_point',
'geojson_multipoint',
'geojson_polygon',
'geojson_polygon_with_hole',
'geojson_multipolygon',
'geojson_linestring',
'geojson_multilinestring',
])
def test_hearing_bbox_filtering_geometries(
request, api_client, random_hearing,
geometry_fixture_name, bbox_containing_geometries):
geometry = request.getfixturevalue(geometry_fixture_name)
feature = get_feature_with_geometry(geometry)
random_hearing.geojson = feature
random_hearing.save()
bbox_query = '?bbox=%s' % bbox_containing_geometries
data = get_data_from_response(api_client.get(list_endpoint + bbox_query))
assert len(data['results']) == 1
assert data['results'][0]['id'] == random_hearing.pk


@pytest.mark.django_db
def test_hearing_copy(default_hearing, random_label):
Section.objects.create(
Expand Down
15 changes: 12 additions & 3 deletions democracy/utils/geo.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@

import json

from django.contrib.gis.geos import GEOSGeometry
from django.contrib.gis.geos import GEOSGeometry, GeometryCollection


def get_geometry_from_geojson(geojson):
gc = GeometryCollection()
if geojson is None:
return None
geometry_data = geojson.get('geometry', None) or geojson
geometry = GEOSGeometry(json.dumps(geometry_data))
return geometry

if geometry_data.get('features'):
for feature in geometry_data.get('features'):
feature_geometry = feature.get('geometry')
feature_GEOS = GEOSGeometry(json.dumps(feature_geometry))
gc.append(feature_GEOS)
else:
geometry = GEOSGeometry(json.dumps(geometry_data))
gc.append(geometry)
return gc
18 changes: 17 additions & 1 deletion democracy/views/hearing.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from rest_framework.settings import api_settings

from democracy.enums import InitialSectionType
from democracy.models import ContactPerson, Hearing, Label, Section, SectionImage, Project
from democracy.models import ContactPerson, Hearing, Label, Section, SectionImage, Project, Organization
from democracy.pagination import DefaultLimitPagination
from democracy.renderers import GeoJSONRenderer
from democracy.views.base import AdminsSeeUnpublishedMixin
Expand Down Expand Up @@ -165,6 +165,7 @@ def create(self, validated_data):
sections_data = validated_data.pop('sections')
project_data = validated_data.pop('project', None)
validated_data['organization'] = self.context['request'].user.get_default_organization()
validated_data['created_by_id'] = self.context['request'].user.id
hearing = super().create(validated_data)
self._create_or_update_sections(hearing, sections_data, force_create=True)
self._create_or_update_project(hearing, project_data)
Expand All @@ -188,6 +189,7 @@ def update(self, instance, validated_data):

sections_data = validated_data.pop('sections')
project_data = validated_data.pop('project', None)
validated_data['modified_by_id'] = self.context['request'].user.id
hearing = super().update(instance, validated_data)
sections = self._create_or_update_sections(hearing, sections_data)
self._create_or_update_project(hearing, project_data)
Expand Down Expand Up @@ -410,6 +412,20 @@ def get_serializer_class(self, *args, **kwargs):
def filter_queryset(self, queryset):
next_closing = self.request.query_params.get('next_closing', None)
open = self.request.query_params.get('open', None)
created_by = self.request.query_params.get('created_by', None)

if created_by is not None and self.request.user:
if created_by.lower() == 'me':
queryset = queryset.filter(created_by_id=self.request.user.id)
else:
try:
organizationObject = Organization.objects.get(name=created_by)
except Organization.DoesNotExist:
organizationObject = None

if organizationObject is not None:
queryset = queryset.filter(organization=organizationObject.id)

if next_closing is not None:
# sliced querysets cannot be filtered or ordered further
return queryset.filter(close_at__gt=next_closing).order_by('close_at')[:1]
Expand Down
Loading

0 comments on commit 45c0e8e

Please sign in to comment.