Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10"]
thing-to-test: ["flake8", "4.2", "5.2"]
python-version: ["3.13"]
thing-to-test: ["flake8", "4.2", "5.2", "6"]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: Set up python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

Expand Down
108 changes: 12 additions & 96 deletions mapit/djangopatch.py
Original file line number Diff line number Diff line change
@@ -1,104 +1,20 @@
# From https://gist.github.com/bpartridge/26a11b28415d706bfb9993fc28767d68

import django


def patch_geos_signatures():
"""
Patch GEOS to function on macOS arm64 and presumably
other odd architectures by ensuring that call signatures
are explicit, and that Django 4 bugfixes are backported.

Should work on Django 2.2+, minimally tested, caveat emptor.
"""
import logging

from ctypes import POINTER, c_uint, c_int
from django.contrib.gis.geos import GeometryCollection, Polygon
from django.contrib.gis.geos import prototypes as capi
from django.contrib.gis.geos.prototypes import GEOM_PTR
from django.contrib.gis.geos.prototypes.geom import GeomOutput
from django.contrib.gis.geos.libgeos import geos_version, lgeos
from django.contrib.gis.geos.linestring import LineString

logger = logging.getLogger("geos_patch")

_geos_version = geos_version()
logger.debug("GEOS: %s %s", _geos_version, repr(lgeos))

# Backport https://code.djangoproject.com/ticket/30274
def new_linestring_iter(self):
for i in range(len(self)):
yield self[i]

LineString.__iter__ = new_linestring_iter

# macOS arm64 requires that we have explicit argtypes for cffi calls.
# Patch in argtypes for `create_polygon` and `create_collection`,
# and then ensure their prep functions do NOT use byref so that the
# arrays (`(GEOM_PTR * length)(...)`) auto-convert into `Geometry**`.
# create_empty_polygon doesn't need to be patched as it takes no args.

# Geometry*
# GEOSGeom_createPolygon_r(GEOSContextHandle_t extHandle,
# Geometry* shell, Geometry** holes, unsigned int nholes)
capi.create_polygon = GeomOutput(
"GEOSGeom_createPolygon", argtypes=[GEOM_PTR, POINTER(GEOM_PTR), c_uint]
)
def patch_set_3d():
from django.contrib.gis.gdal import OGRGeometry

# Geometry*
# GEOSGeom_createCollection_r(GEOSContextHandle_t extHandle,
# int type, Geometry** geoms, unsigned int ngeoms)
capi.create_collection = GeomOutput(
"GEOSGeom_createCollection", argtypes=[c_int, POINTER(GEOM_PTR), c_uint]
)

# The below implementations are taken directly from Django 2.2.25 source;
# the only changes are unwrapping calls to byref().

def new_create_polygon(self, length, items):
# Instantiate LinearRing objects if necessary, but don't clone them yet
# _construct_ring will throw a TypeError if a parameter isn't a valid ring
# If we cloned the pointers here, we wouldn't be able to clean up
# in case of error.
if not length:
return capi.create_empty_polygon()

rings = []
for r in items:
if isinstance(r, GEOM_PTR):
rings.append(r)
else:
rings.append(self._construct_ring(r))

shell = self._clone(rings.pop(0))

n_holes = length - 1
if n_holes:
holes = (GEOM_PTR * n_holes)(*[self._clone(r) for r in rings])
holes_param = holes
def set_3d(self, value):
"""Set if this geometry has Z coordinates."""
if value is True:
self.coord_dim = 3
elif value is False:
self.coord_dim = 2
else:
holes_param = None

return capi.create_polygon(shell, holes_param, c_uint(n_holes))

Polygon._create_polygon = new_create_polygon

# Need to patch to not call byref so that we can cast to a pointer
def new_create_collection(self, length, items):
# Creating the geometry pointer array.
geoms = (GEOM_PTR * length)(
*[
# this is a little sloppy, but makes life easier
# allow GEOSGeometry types (python wrappers) or pointer types
capi.geom_clone(getattr(g, "ptr", g))
for g in items
]
)
return capi.create_collection(c_int(self._typeid), geoms, c_uint(length))
raise ValueError(f"Input to 'set_3d' must be a boolean, got '{value!r}'.")

GeometryCollection._create_collection = new_create_collection
OGRGeometry.set_3d = set_3d


if django.get_version() < '4.0.1':
patch_geos_signatures()
if django.get_version() < '5.1':
patch_set_3d()
2 changes: 1 addition & 1 deletion mapit/management/command_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def save_polygons(lookup, write_to_stdout=True):
if g.point_count < 4:
continue
# Make sure it is two-dimensional
g.coord_dim = 2
g.set_3d(False)
m.polygons.create(polygon=g.wkb)
# m.polygon = g.wkt
# m.save()
Expand Down
9 changes: 4 additions & 5 deletions mapit/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@


def materialized():
version = connection.cursor().connection.server_version
materialized = ''
if version >= 120000:
if connection.pg_version >= 120000:
materialized = 'MATERIALIZED'
return materialized

Expand Down Expand Up @@ -191,8 +190,8 @@ def intersect(self, query_type, area, types, generation):
params = [area.id, area.id, generation.id, generation.id]

if types:
params.append(tuple(types))
query_area_type = ' AND mapit_area.type_id IN (SELECT id FROM mapit_type WHERE code IN %s) '
params.append(list(types))
query_area_type = ' AND mapit_area.type_id IN (SELECT id FROM mapit_type WHERE code = ANY(%s)) '
else:
query_area_type = ''

Expand Down Expand Up @@ -452,7 +451,7 @@ def filter_by_area(self, area, limit=''):
WHERE area_id = %s'''
query = '''
WITH target AS %s ( %s )
SELECT "mapit_postcode"."id", "mapit_postcode"."postcode", "mapit_postcode"."location"::bytea
SELECT "mapit_postcode"."id", "mapit_postcode"."postcode", "mapit_postcode"."location"
FROM mapit_postcode, target
WHERE ST_CoveredBy(location, target.division)
%s
Expand Down
2 changes: 1 addition & 1 deletion mapit/views/areas.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import re
from psycopg2 import InternalError
from django.db import InternalError
from django.db.utils import DatabaseError

from django.utils.translation import gettext as _
Expand Down
2 changes: 1 addition & 1 deletion mapit_gb/management/commands/mapit_UK_add_ons_to_gss.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import sys
from django.core.management.base import BaseCommand
from mapit.models import Area, CodeType
from psycopg2 import IntegrityError
from django.db import IntegrityError

python_version = sys.version_info[0]

Expand Down
2 changes: 1 addition & 1 deletion mapit_gb/management/commands/mapit_UK_update_ons_ids.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import csv
from django.core.management.base import BaseCommand
from mapit.models import Area, Generation, CodeType
from psycopg2 import IntegrityError
from django.db import IntegrityError


class Command(BaseCommand):
Expand Down
2 changes: 1 addition & 1 deletion mapit_gb/management/commands/mapit_UK_update_ons_ids2.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import csv
from django.core.management.base import BaseCommand
from mapit.models import Area, Generation, CodeType
from psycopg2 import IntegrityError
from django.db import IntegrityError


class Command(BaseCommand):
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ def read_file(filename):
scripts=['bin/mapit_make_css'],
include_package_data=True,
install_requires=[
'Django >= 4.2, <6.0',
'Django >= 4.2, <7.0',
'libsass >= 0.13.3',
'psycopg2',
'psycopg',
'PyYAML',
'Shapely',
'uk-postcode-utils',
Expand Down
10 changes: 6 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
[tox]
envlist = flake8, py310-{4.2,5.2}
envlist = flake8, py313-{4.2,5.2,6}

[testenv]
commands =
flake8: flake8 mapit mapit_gb mapit_it mapit_no mapit_se mapit_za project
py310: python -W all -W ignore::PendingDeprecationWarning -m coverage run --source mapit manage.py test mapit mapit_gb
py313: python -W all -W ignore::PendingDeprecationWarning -m coverage run --source mapit manage.py test mapit mapit_gb
deps =
py310: coverage
py313: coverage
flake8: flake8
4.2: Django>=4.2,<5.0
5.2: Django>=5.2,<6.0
6: Django>=6.0,<7.0
passenv =
CFLAGS
PYTHONWARNINGS
Expand All @@ -21,10 +22,11 @@ skip_install = True

[gh-actions]
python =
3.10: flake8, py310
3.13: flake8, py313

[gh-actions:env]
THING_TO_TEST =
flake8: flake8
4.2: 4.2
5.2: 5.2
6: 6