Skip to content

Commit

Permalink
bplan: make some further adjustments to the bplan api to fix some iss…
Browse files Browse the repository at this point in the history
…ue with diplan
  • Loading branch information
goapunk committed Dec 18, 2024
1 parent ccb1ca4 commit ef18340
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 39 deletions.
2 changes: 2 additions & 0 deletions changelog/8543.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@
- keep compatibility with the imperia system for now but annotated code which
can be removed once the transition to diplan is completed
- update the bplan api documentation
- add a `bplan_id` field which will replace the `identifier` field once the
switch to diplan is done
35 changes: 16 additions & 19 deletions docs/bplan_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,12 @@ The following fields need to be provided:
- *name*: string
- Name of the BPLAN (e.g. used as the title of the project tile)
- Maximum length of 120 chars
- *identifier*: string
- *(imperia only) identifier*: string
- Identifier that clearly identifies the BPLAN, needs to be the same as in the FIS Broker (e.g. `VIII - 329`)
- Maximum length of 120 chars
- (diplan only) bplan_id*: string
- Id that clearly identifies the BPLAN, needs to be the same as in the FIS Broker (e.g. `VIII - 329`)
- Maximum length of 120 chars
- *description*: string
- Description of the BPLAN shown in the project tile
- Maximum length of 250 chars
Expand All @@ -98,9 +101,10 @@ The following fields need to be provided:
- End date of the participation in
- [ISO 8601 format](https://en.wikipedia.org/wiki/ISO_8601)
(if no time zone is defined, german time zones UTC+01 and UTC+02 are used)
- *point*: geojson
- *point*: string
- string containing coordinates separated by a comma, e.g. "1492195.544958444,6895923.461738203"
- Location of the bplan
- Projection: WGS84 / EPSG:4326 or EPSG:3857
- Projection: WGS84 / EPSG:3857
- *image_url*: string
- URL of the image that is used in the project tile
- Minimal resolution 500x300 px (width x height)
Expand All @@ -114,22 +118,13 @@ The following fields need to be provided:
```json
{
"name": "Luisenblock Ost - Bebauungsplan 1-70",
"identifier": "VI - 123c",
"bplan_id": "VI - 123c",
"description": "Der Luisenblock Ost soll städtebaulich neu geordnet werden. Nutzungen des Deutschen Bundestages sollen in einem Sondergebiet als Auftakt des 'Band des Bundes' zusammengefasst werden.",
"url": "https://berlin.de/ba-marzahn-hellersdorf/.../bebauungsplan.649020.php",
"is_draft": false,
"start_date": "2017-01-01T00:00",
"end_date": "2018-01-01T00:00",
"point": {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
13.411924777644563,
52.499598134440944
]
}
},
"point": "1492195.544958444,6895923.461738203",
"image_url": "http://berlin.de/images/.../bebauungsplan.649020.png",
"image_copyright": "BA Marzahn-Hellersdorf"
}
Expand Down Expand Up @@ -185,13 +180,13 @@ curl \
'
{
"name":"Luisenblock Ost - Bebauungsplan 1-70",
"identifier": "VI - 96a",
"bplan_id": "VI - 96a",
"description": "Test",
"url": "https://mein.berlin.de",
"office_worker_email": "[email protected]",
"start_date": "2019-01-01T00:00",
"end_date": "2022-01-01T00:00",
"point": {"type": "Feature","geometry": {"type": "Point", "coordinates":[13.411924777644563,52.499598134440944]}}
"point": "1492195.544958444,6895923.461738203"
}
'
```
Expand All @@ -206,20 +201,22 @@ curl -X POST http://127.0.0.1:8003/api/organisations/1/bplan/ \
'
{
"name":"Luisenblock Ost - Bebauungsplan 1-70",
"identifier": "VI - 96a",
"bplan_id": "VI - 96a",
"description": "Test",
"url": "https://mein.berlin.de",
"office_worker_email": "[email protected]",
"start_date": "2019-01-01T00:00",
"end_date": "2022-01-01T00:00",
"point": {"type": "Feature","geometry": {"type": "Point", "coordinates":[13.411924777644563,52.499598134440944]}}
"point": "1492195.544958444,6895923.461738203"
}
'
```

## Updating a Bplan

Update an existing Bplan with the id `bplan-id` within the organisation designated by`organisation-id`.
Update an existing Bplan with the id `bplan-id` (attention: `bplan-id` here refers to the id which is returned from
the api after creating a new bplan, not the `bplan_id` field which is used to designate the fis-broker identifier)
within the organisation designated by`organisation-id`.

**URL** : `https://mein.berlin.de/api/organisations/<organisation-id>/bplan/<bplan-id>/`

Expand Down
57 changes: 51 additions & 6 deletions meinberlin/apps/bplan/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext as _
from pyproj import Transformer
from rest_framework import serializers

from adhocracy4.dashboard import components
Expand Down Expand Up @@ -62,16 +63,21 @@ class BplanSerializer(serializers.ModelSerializer):
),
)
embed_code = serializers.SerializerMethodField()
# overwrite the point model field so it's expecting json, the original field is validated as a string and therefore
# doesn't pass validation when not receiving a string
point = serializers.JSONField(required=False, write_only=True)
# # overwrite the point model field so it's expecting json, the original field is validated as a string and therefore
# # doesn't pass validation when not receiving a string
# point = serializers.JSONField(required=False, write_only=True)
bplan_id = serializers.CharField(
required=False,
write_only=True,
)

class Meta:
model = Bplan
fields = (
"id",
"name",
"identifier",
"bplan_id",
"description",
"url",
"office_worker_email",
Expand All @@ -94,6 +100,7 @@ class Meta:
"url": {"write_only": True},
"office_worker_email": {"write_only": True},
"identifier": {"write_only": True},
"point": {"write_only": True, "required": False},
}

def to_representation(self, instance):
Expand All @@ -112,9 +119,28 @@ def create(self, validated_data):

# mark as diplan, will make removal of old bplans easier
# TODO: remove this check and the is_diplan field once transition to diplan is completed
if "point" in validated_data:
if "bplan_id" in validated_data or "point" in validated_data:
validated_data["is_diplan"] = True

# TODO: rename identifier to bplan_id on model and remove the custom logic here
if "bplan_id" in validated_data:
bplan_id = validated_data.pop("bplan_id")
validated_data["identifier"] = bplan_id

# We receive the point as a string containing coordinates in epsg3875 but internally
# use epsg4326 so we need to convert them and save them as valid geojson
if "point" in validated_data:
point = validated_data["point"].split(",")
transformer = Transformer.from_crs("EPSG:3857", "EPSG:4326")
new_point = transformer.transform(point[0].strip(), point[1].strip())
validated_data["point"] = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [new_point[1], new_point[0]],
},
}

start_date = validated_data["start_date"]
end_date = validated_data["end_date"]

Expand Down Expand Up @@ -151,15 +177,34 @@ def update(self, instance, validated_data):
if start_date or end_date:
self._update_phase(instance, start_date, end_date)
# TODO: remove as we don't need to archive bplans anymore once the transition to diplan is complete as
# they depublish them if they should no longer be shown
# they unpublish them if they should no longer be shown
if end_date and end_date > timezone.localtime(timezone.now()):
instance.is_archived = False

# mark as diplan, will make removal of old bplans easier
# TODO: remove this check and the is_diplan field once transition to diplan is completed
if "point" in validated_data:
if "bplan_id" in validated_data or "point" in validated_data:
validated_data["is_diplan"] = True

# TODO: rename identifier to bplan_id on model and remove the custom logic here
if "bplan_id" in validated_data:
bplan_id = validated_data.pop("bplan_id")
validated_data["identifier"] = bplan_id

# We receive the point as a string containing coordinates in epsg3875 but internally
# use epsg4326 so we need to convert them and save them as valid geojson
if "point" in validated_data:
point = validated_data["point"].split(",")
transformer = Transformer.from_crs("EPSG:3857", "EPSG:4326")
new_point = transformer.transform(point[0].strip(), point[1].strip())
validated_data["point"] = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [new_point[1], new_point[0]],
},
}

image_url = validated_data.pop("image_url", None)
if image_url:
validated_data["tile_image"] = self._download_image_from_url(image_url)
Expand Down
1 change: 1 addition & 0 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ brotli==1.1.0
django-celery-beat==2.7.0
django-cloudflare-push==0.2.2
django_csp==3.8
pyproj==3.7.0
redis==5.2.0
requests==2.32.3
sentry-sdk[django]==2.19.0
Expand Down
52 changes: 38 additions & 14 deletions tests/bplan/test_bplan_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,8 @@ def test_add_bplan_diplan_response_no_embed(apiclient, districts, organisation):
"url": "https://bplan.net",
"start_date": "2013-01-01 18:00",
"end_date": "2021-01-01 18:00",
"identifier": "1-234",
"point": "[0,0]",
"bplan_id": "1-234",
"point": "0,0",
}
user = organisation.initiators.first()
apiclient.force_authenticate(user=user)
Expand Down Expand Up @@ -334,6 +334,29 @@ def test_bplan_api_adds_district_from_identifier(apiclient, districts, organisat
assert bplan.administrative_district.name == "Mitte"


@pytest.mark.django_db
def test_bplan_api_diplan_adds_district_from_bplan_id(
apiclient, districts, organisation
):
url = reverse("bplan-list", kwargs={"organisation_pk": organisation.pk})
data = {
"name": "bplan-1",
"description": "desc",
"url": "https://bplan.net",
"office_worker_email": "[email protected]",
"start_date": "2013-01-01 18:00",
"bplan_id": "1-234",
"point": "0,0",
"end_date": "2021-01-01 18:00",
}
user = organisation.initiators.first()
apiclient.force_authenticate(user=user)
response = apiclient.post(url, data, format="json")
assert response.status_code == status.HTTP_201_CREATED
bplan = bplan_models.Bplan.objects.first()
assert bplan.administrative_district.name == "Mitte"


@pytest.mark.django_db
def test_bplan_api_adds_district_from_identifier_with_whitespaces(
apiclient, districts, organisation
Expand Down Expand Up @@ -387,7 +410,7 @@ def test_bplan_api_adds_is_diplan_if_point_is_sent(apiclient, districts, organis
"url": "https://bplan.net",
"start_date": "2013-01-01 18:00",
"identifier": "1-234",
"point": "[0,0]",
"point": "0,0",
"end_date": "2021-01-01 18:00",
}
user = organisation.initiators.first()
Expand Down Expand Up @@ -439,7 +462,7 @@ def test_bplan_api_location_task_not_called_if_point_included(
"end_date": "2021-01-01 18:00",
"identifier": "1-234",
"is_published": True,
"point": "[0,0]",
"point": "0,0",
}
user = organisation.initiators.first()
apiclient.force_authenticate(user=user)
Expand All @@ -449,12 +472,18 @@ def test_bplan_api_location_task_not_called_if_point_included(
bplan = bplan_models.Bplan.objects.first()
assert bplan.is_draft is False
assert bplan.is_diplan is True
assert bplan.point == "[0,0]"
assert bplan.point == {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [0.0, 0.0],
},
}
mock.assert_not_called()


@pytest.mark.django_db
def test_bplan_api_accepts_valid_geojson(
def test_bplan_api_accepts_string_as_point_and_converts_to_epsg4326(
apiclient, districts, organisation, django_capture_on_commit_callbacks
):
url = reverse("bplan-list", kwargs={"organisation_pk": organisation.pk})
Expand All @@ -466,14 +495,9 @@ def test_bplan_api_accepts_valid_geojson(
"end_date": "2021-01-01 18:00",
"identifier": "1-234",
"is_published": True,
"point": {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [13.411924777644563, 52.499598134440944],
},
},
"point": "1492195.544958444,6895923.461738203",
}

user = organisation.initiators.first()
apiclient.force_authenticate(user=user)
with django_capture_on_commit_callbacks(execute=True):
Expand All @@ -486,6 +510,6 @@ def test_bplan_api_accepts_valid_geojson(
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [13.411924777644563, 52.499598134440944],
"coordinates": [13.404620649312287, 52.526688152152744],
},
}

0 comments on commit ef18340

Please sign in to comment.