Skip to content

Commit

Permalink
Merge branch 'main' into flatgeobuf-renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
why-not-try-calmer committed Sep 29, 2023
2 parents 442b59f + 0aa85dd commit b4752fb
Show file tree
Hide file tree
Showing 24 changed files with 597 additions and 39 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/conformance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,10 @@ jobs:
# deploy static files and migrate database
docker compose exec django python manage.py collectstatic --no-input
docker compose exec django python manage.py migrate --no-input
docker compose exec django python manage.py populate_vl
docker compose exec django python manage.py populate_signs_poles
docker compose exec django python manage.py populate_data
- name: Healthcheck
run: wget --no-check-certificate https://localhost/oapif/collections/signalo_core.pole/items
run: wget --no-check-certificate https://localhost/oapif/collections/tests.point_2056_10fields/items

- name: Run conformance test suite
run: docker compose run conformance_test
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@ jobs:
docker-compose exec django python manage.py migrate --no-input
docker-compose exec django python manage.py collectstatic --no-input
docker-compose exec django python manage.py populate_users
docker-compose exec django python manage.py populate_vl
docker-compose exec django python manage.py populate_signs_poles
docker-compose exec django python manage.py populate_edge_cases
docker-compose exec django python manage.py populate_roads
docker-compose exec django python manage.py populate_data
- name: Failure logs
if: failure()
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ services:
- 443:443

django:
image: opengisch/signalo-oapif:latest
image: opengisch/django-oapif:latest
build:
context: .
dockerfile: docker/django/Dockerfile
cache_from:
- opengisch/signalo-oapif:latest
- opengisch/django-oapif:latest
restart: unless-stopped
depends_on:
postgres:
Expand Down
2 changes: 1 addition & 1 deletion docker/django/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pull official base image
FROM python:3.9-buster
FROM python:3.10-buster

# set work directory
WORKDIR /usr/src/app
Expand Down
30 changes: 15 additions & 15 deletions docker/integration-tests/test_integration_qgis.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

ROOT_URL = "http://django:8000/oapif/"
COLLECTIONS_URL = "http://django:8000/oapif/collections"
POLES_URL = "http://django:8000/oapif/collections/signalo_core.pole"
POINTS_URL = "http://django:8000/oapif/collections/tests.point_2056_10fields"


class TestStack(unittest.TestCase):
Expand All @@ -27,36 +27,36 @@ def setUpClass(cls):
def test_endpoint_ok(self):
root_response = requests.get(ROOT_URL)
collections_response = requests.get(COLLECTIONS_URL)
poles_response = requests.get(POLES_URL)
points_response = requests.get(POINTS_URL)

self.assertTrue(root_response.status_code == 200)
self.assertTrue(collections_response.status_code == 200)
self.assertTrue(poles_response.status_code == 200)
self.assertTrue(points_response.status_code == 200)

def test_collection_exists(self):
res = requests.get(COLLECTIONS_URL).json()
self.assertTrue(
"signalo_core.pole"
"tests.point_2056_10fields"
in [collection["id"] for collection in res["collections"]]
)

def test_many_poles(self):
poles = requests.get(POLES_URL).json()
self.assertTrue(len(poles) > 1)
def test_many_points(self):
points = requests.get(POINTS_URL).json()
self.assertTrue(len(points) > 1)

def test_load_layer(self):
uri = QgsDataSourceUri()
uri.setParam("service", "wfs")
uri.setParam("typename", "signalo_core.pole")
uri.setParam("typename", "tests.point_2056_10fields")
uri.setParam("url", ROOT_URL)
layer = QgsVectorLayer(uri.uri(), "pole", "OAPIF")
layer = QgsVectorLayer(uri.uri(), "point", "OAPIF")
self.assertTrue(layer.isValid())

layer = self.project.addMapLayer(layer)
self.assertIsNotNone(layer)

f = None
for f in layer.getFeatures("name='1-1'"):
for f in layer.getFeatures("field_0 is not null"):
pass
self.assertIsInstance(f, QgsFeature)

Expand All @@ -70,12 +70,12 @@ def test_load_layer(self):
def test_load_with_basic_auth(self):
uri = QgsDataSourceUri()
uri.setParam("service", "wfs")
uri.setParam("typename", "signalo_core.pole")
uri.setParam("typename", "tests.point_2056_10fields")
uri.setParam("url", ROOT_URL)
uri.setPassword(self.password)
uri.setUsername(self.user)

layer = QgsVectorLayer(uri.uri(), "pole", "OAPIF")
layer = QgsVectorLayer(uri.uri(), "point", "OAPIF")
self.assertTrue(layer.isValid())
layer = self.project.addMapLayer(layer)
self.assertIsNotNone(layer)
Expand All @@ -88,15 +88,15 @@ def test_load_with_basic_auth(self):
)

f = None
for f in layer.getFeatures("name='1-1'"):
for f in layer.getFeatures():
pass
self.assertIsInstance(f, QgsFeature)

f["name"] = "xyz"
f["field_1"] = "xyz"
with edit(layer):
layer.updateFeature(f)

f = None
for f in layer.getFeatures("name='xyz'"):
for f in layer.getFeatures("field_1='xyz'"):
pass
self.assertIsInstance(f, QgsFeature)
23 changes: 11 additions & 12 deletions scripts/restart.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,22 @@ set -e

FULL=$1

SRID=4326
#if [[ $FULL == "reset" ]];then
#rm signalo/signalo_app/migrations/00*.py || true
#fi

if [[ $FULL == "reset" ]];then
rm signalo/signalo_app/migrations/00*.py || true
./scripts/fixture-generator.py -s $SRID -m 100
fi
export COMPOSE_FILE=docker-compose.dev.yml:docker-compose.yml

docker-compose down --volumes || true
docker compose down --volumes || true

docker-compose up --build -d
docker compose up --build --force-recreate -d
sleep 5

if [[ $FULL == "reset" ]];then
docker-compose exec django python manage.py makemigrations
docker-compose exec django python manage.py migrate
docker compose exec django python manage.py makemigrations
docker compose exec django python manage.py migrate
fi

docker-compose exec django python manage.py loaddata pole
docker-compose exec django python manage.py loaddata sign
docker-compose exec django python manage.py updatedata
docker compose exec django python manage.py collectstatic --no-input
docker compose exec django python manage.py populate_users
docker compose exec django python manage.py populate_data
14 changes: 13 additions & 1 deletion src/django_oapif/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ class Meta:
viewset_oapif_geom_lookup = (
"geom" # one day this will be retrieved automatically from the serializer
)
_geo_field = "geom"
if skip_geom:
_viewset_oapif_geom_lookup = None
_geo_field = None

class AutoSerializer(GeoFeatureModelSerializer):
class Meta:
nonlocal _geo_field

model = Model
fields = "__all__"
geo_field = _geo_field

# Create the viewset
class Viewset(OAPIFDescribeModelViewSetMixin, viewsets.ModelViewSet):
Expand All @@ -78,7 +90,7 @@ class Viewset(OAPIFDescribeModelViewSetMixin, viewsets.ModelViewSet):
oapif_description = Model.__doc__

# (one day this will be retrieved automatically from the serializer)
oapif_geom_lookup = viewset_oapif_geom_lookup
oapif_geom_lookup = _viewset_oapif_geom_lookup
filter_backends = [BboxFilterBackend]

# Allowing '.' and '-' in urls
Expand Down
2 changes: 2 additions & 0 deletions src/django_oapif/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
class OapifPagination(pagination.LimitOffsetPagination):
"""OAPIF-compatible django rest paginator"""

default_limit = 1000

def get_paginated_response(self, data):
return Response(
{
Expand Down
2 changes: 1 addition & 1 deletion src/manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "signalo.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
Expand Down
1 change: 1 addition & 0 deletions src/signalo/roads/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from django_oapif.pagination import OapifPagination
from django_oapif.decorators import register_oapif_viewset
from django_oapif.pagination import OapifPagination
from signalo.settings import GEOMETRY_SRID


Expand Down
Empty file added src/tests/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions src/tests/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.contrib import admin

from .models import Point_2056_10fields

admin.site.register(Point_2056_10fields)
6 changes: 6 additions & 0 deletions src/tests/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class TestsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "tests"
16 changes: 16 additions & 0 deletions src/tests/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
ASGI config for django oapif project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings")

application = get_asgi_application()
65 changes: 65 additions & 0 deletions src/tests/management/commands/populate_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import math
import random
import string

from django.core.management import call_command
from django.core.management.base import BaseCommand
from django.db import transaction

from tests.models import Line_2056_10fields, NoGeom_10fields, Point_2056_10fields


class Command(BaseCommand):
help = "Populate db with testdata"

def add_arguments(self, parser):
parser.add_argument("-s", "--size", type=int, default=10000)

@transaction.atomic
def handle(self, *args, **options):
"""Populate db with testdata"""
size = options["size"]
x_start = 2508500
y_start = 1152000
step = 100

magnitude = math.ceil(math.sqrt(size))

points = []
lines = []
no_geoms = []

letters = string.ascii_lowercase

for dx in range(magnitude):
for dy in range(magnitude):
x = x_start + dx * step
y = y_start + dy * step
geom_pt_wkt = f"Point({x:4f} {y:4f})"
geom_line_wkt = f"LineString({x:4f} {y:4f}, {x+random.randint(10,50):4f} {y+random.randint(10,50):4f})"

fields = {}
for f in range(10):
fields[f"field_{f}"] = "".join(
random.choice(letters) for i in range(10)
)

no_geom = NoGeom_10fields(**fields)
no_geoms.append(no_geom)

fields["geom"] = geom_pt_wkt
point = Point_2056_10fields(**fields)
points.append(point)

fields["geom"] = geom_line_wkt
line = Line_2056_10fields(**fields)
lines.append(line)

# Create objects in batches
Point_2056_10fields.objects.bulk_create(points)
NoGeom_10fields.objects.bulk_create(no_geoms)
Line_2056_10fields.objects.bulk_create(lines)

# Call 'update_data' to update computed properties
call_command("updatedata")
print(f"🤖 testdata added!")
50 changes: 50 additions & 0 deletions src/tests/management/commands/populate_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from django.contrib.auth.models import Group, Permission, User
from django.core.management.base import BaseCommand
from django.db import transaction


class Command(BaseCommand):
help = "Populate db with groups, set permissions, add users"

@transaction.atomic
def handle(self, *args, **options):
"""Populate db with groups, set permissions, add users"""
adding = []
modifying = []
viewing = []

for model in ("point_2056_10fields", "nogeom_10fields", "line_2056_10fields"):
adding.append(Permission.objects.get(codename=f"add_{model}"))
modifying.append(Permission.objects.get(codename=f"change_{model}"))
viewing.append(Permission.objects.get(codename=f"view_{model}"))

editing = adding + modifying + viewing

editors, _ = Group.objects.get_or_create(name="editors")
viewers, _ = Group.objects.get_or_create(name="viewers")
viewers_wo_lines, _ = Group.objects.get_or_create(name="viewers_without_lines")

editors.save()
viewers.save()

editors.permissions.set(editing)
viewers.permissions.set(viewing)

viewer, _ = User.objects.get_or_create(username="demo_viewer")
viewer_wo_lines, _ = User.objects.get_or_create(
username="demo_viewer_without_lines"
)
editor, _ = User.objects.get_or_create(username="demo_editor")
super_user = User.objects.create_superuser(username="admin", is_staff=True)

for user in (viewer, viewer_wo_lines, editor, super_user):
user.set_password("123")
user.save()

editor.groups.add(editors)
viewer.groups.add(viewers)
viewer_wo_lines.groups.add(viewers_wo_lines)

print(
f"👥 added users 'demo_editor' & 'demo_viewer' to group 'editors' and 'viewers' respectively. Permissions set accordingly."
)
Loading

0 comments on commit b4752fb

Please sign in to comment.