Skip to content

Commit 50550c4

Browse files
committed
Add GeoDjango support
1 parent e05c4cc commit 50550c4

File tree

21 files changed

+529
-7
lines changed

21 files changed

+529
-7
lines changed

.github/workflows/runtests.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import pathlib
44
import sys
55

6+
from django.core.exceptions import ImproperlyConfigured
7+
68
test_apps = [
79
"admin_changelist",
810
"admin_checks",
@@ -157,6 +159,15 @@
157159
]
158160
),
159161
]
162+
163+
try:
164+
from django.contrib.gis.gdal import GDAL_VERSION # noqa: F401
165+
except ImproperlyConfigured:
166+
# GDAL not installed.
167+
pass
168+
else:
169+
test_apps.extend(["gis_tests", "gis_tests_"])
170+
160171
runtests = pathlib.Path(__file__).parent.resolve() / "runtests.py"
161172
run_tests_cmd = f"python3 {runtests} %s --settings mongodb_settings -v 2"
162173

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Identical to test-python-atlas.yml except that gdal-bin is also installed.
2+
name: Python Tests on Atlas with GeoDjango
3+
4+
on:
5+
pull_request:
6+
paths:
7+
- '**.py'
8+
- '!setup.py'
9+
- '.github/workflows/test-python-atlas-geo.yml'
10+
workflow_dispatch:
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.ref }}
14+
cancel-in-progress: true
15+
16+
defaults:
17+
run:
18+
shell: bash -eux {0}
19+
20+
jobs:
21+
build:
22+
name: Django Test Suite
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Checkout django-mongodb-backend
26+
uses: actions/checkout@v4
27+
with:
28+
persist-credentials: false
29+
- name: install django-mongodb-backend
30+
run: |
31+
pip3 install --upgrade pip
32+
pip3 install -e .
33+
- name: Checkout Django
34+
uses: actions/checkout@v4
35+
with:
36+
repository: 'mongodb-forks/django'
37+
ref: 'mongogis'
38+
path: 'django_repo'
39+
persist-credentials: false
40+
- name: Install system packages for Django's Python test dependencies
41+
run: |
42+
sudo apt-get update
43+
sudo apt-get install gdal-bin libmemcached-dev
44+
- name: Install Django and its Python test dependencies
45+
run: |
46+
cd django_repo/tests/
47+
pip3 install -e ..
48+
pip3 install -r requirements/py3.txt
49+
- name: Copy the test settings file
50+
run: cp .github/workflows/mongodb_settings.py django_repo/tests/
51+
- name: Copy the test runner file
52+
run: cp .github/workflows/runtests.py django_repo/tests/runtests_.py
53+
- name: Start local Atlas
54+
working-directory: .
55+
run: bash .github/workflows/start_local_atlas.sh mongodb/mongodb-atlas-local:7
56+
- name: Run tests
57+
run: python3 django_repo/tests/runtests.py --settings mongodb_settings -v 2

.github/workflows/test-python-geo.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Identical to test-python.yml except that gdal-bin is also installed.
2+
name: Python Tests with GeoDjango
3+
4+
on:
5+
pull_request:
6+
paths:
7+
- '**.py'
8+
- '!setup.py'
9+
- '.github/workflows/test-python-geo.yml'
10+
workflow_dispatch:
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.ref }}
14+
cancel-in-progress: true
15+
16+
defaults:
17+
run:
18+
shell: bash -eux {0}
19+
20+
jobs:
21+
build:
22+
name: Django Test Suite
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Checkout django-mongodb-backend
26+
uses: actions/checkout@v4
27+
with:
28+
persist-credentials: false
29+
- name: install django-mongodb-backend
30+
run: |
31+
pip3 install --upgrade pip
32+
pip3 install -e .
33+
- name: Checkout Django
34+
uses: actions/checkout@v4
35+
with:
36+
repository: 'mongodb-forks/django'
37+
ref: 'mongogis'
38+
path: 'django_repo'
39+
persist-credentials: false
40+
- name: Install system packages for Django's Python test dependencies
41+
run: |
42+
sudo apt-get update
43+
sudo apt-get install gdal-bin libmemcached-dev
44+
- name: Install Django and its Python test dependencies
45+
run: |
46+
cd django_repo/tests/
47+
pip3 install -e ..
48+
pip3 install -r requirements/py3.txt
49+
- name: Copy the test settings file
50+
run: cp .github/workflows/mongodb_settings.py django_repo/tests/
51+
- name: Copy the test runner file
52+
run: cp .github/workflows/runtests.py django_repo/tests/runtests_.py
53+
- name: Start MongoDB
54+
uses: supercharge/mongodb-github-action@90004df786821b6308fb02299e5835d0dae05d0d # 1.12.0
55+
with:
56+
mongodb-version: 6.0
57+
- name: Run tests
58+
run: python3 django_repo/tests/runtests_.py

django_mongodb_backend/features.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
from django.core.exceptions import ImproperlyConfigured
12
from django.db.backends.base.features import BaseDatabaseFeatures
23
from django.utils.functional import cached_property
34
from pymongo.errors import OperationFailure
45

6+
try:
7+
from .gis.features import GISFeatures
8+
except ImproperlyConfigured:
9+
# GIS libraries (GDAL/GEOS) not installed.
10+
class GISFeatures:
11+
pass
512

6-
class DatabaseFeatures(BaseDatabaseFeatures):
13+
14+
class DatabaseFeatures(GISFeatures, BaseDatabaseFeatures):
715
minimum_database_version = (6, 0)
816
allow_sliced_subqueries_with_in = False
917
allows_multiple_constraints_on_same_fields = False
@@ -135,7 +143,7 @@ def django_test_expected_failures(self):
135143
expected_failures.update(self._django_test_expected_failures_no_transactions)
136144
return expected_failures
137145

138-
django_test_skips = {
146+
_django_test_skips = {
139147
"Database defaults aren't supported by MongoDB.": {
140148
# bson.errors.InvalidDocument: cannot encode object:
141149
# <django.db.models.expressions.DatabaseDefault
@@ -588,6 +596,12 @@ def django_test_expected_failures(self):
588596
},
589597
}
590598

599+
@cached_property
600+
def django_test_skips(self):
601+
skips = super().django_test_skips
602+
skips.update(self._django_test_skips)
603+
return skips
604+
591605
@cached_property
592606
def is_mongodb_6_3(self):
593607
return self.connection.get_database_version() >= (6, 3)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.core.exceptions import ImproperlyConfigured
2+
3+
try:
4+
from .lookups import register_lookups
5+
except ImproperlyConfigured:
6+
# GIS libraries (GDAL/GEOS) not installed.
7+
pass
8+
else:
9+
register_lookups()

django_mongodb_backend/gis/adapter.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import collections
2+
3+
4+
class Adapter(collections.UserDict):
5+
srid = 4326
6+
7+
def __init__(self, obj, geography=False):
8+
"""
9+
Initialize on the spatial object.
10+
"""
11+
if obj.__class__.__name__ == "GeometryCollection":
12+
self.data = {
13+
"type": obj.__class__.__name__,
14+
"geometries": [self.get_data(x) for x in obj],
15+
}
16+
else:
17+
self.data = self.get_data(obj)
18+
19+
def get_data(self, obj):
20+
return {
21+
"type": obj.__class__.__name__,
22+
"coordinates": obj.coords,
23+
}
24+
25+
@classmethod
26+
def _fix_polygon(cls, poly):
27+
return poly
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
2+
from django.utils.functional import cached_property
3+
4+
5+
class GISFeatures(BaseSpatialFeatures):
6+
has_spatialrefsys_table = False
7+
supports_transform = False
8+
9+
@cached_property
10+
def django_test_expected_failures(self):
11+
expected_failures = super().django_test_expected_failures
12+
expected_failures.update(
13+
{
14+
# annotate with Value not supported.
15+
# e.g. QuerySet.annotate(p=Value(p, GeometryField(srid=4326)
16+
"gis_tests.geoapp.test_expressions.GeoExpressionsTests.test_geometry_value_annotation",
17+
}
18+
)
19+
return expected_failures
20+
21+
@cached_property
22+
def django_test_skips(self):
23+
skips = super().django_test_skips
24+
skips.update(
25+
{
26+
"inspectdb not supported.": {
27+
"gis_tests.inspectapp.tests.InspectDbTests",
28+
},
29+
"Raw SQL not supported": {
30+
"gis_tests.geoapp.tests.GeoModelTest.test_raw_sql_query",
31+
},
32+
"MongoDB doesn't support the SRID used in this test.": {
33+
# Error messages:
34+
# - Can't extract geo keys
35+
# - Longitude/latitude is out of bounds
36+
"gis_tests.geoapp.test_expressions.GeoExpressionsTests.test_update_from_other_field",
37+
"gis_tests.layermap.tests.LayerMapTest.test_encoded_name",
38+
"gis_tests.relatedapp.tests.RelatedGeoModelTest.test06_f_expressions",
39+
# SouthTexasCity fixture objects use SRID 2278 which is ignored
40+
# by the patched version of loaddata in the Django fork.
41+
"gis_tests.distapp.tests.DistanceTest.test_init",
42+
},
43+
"ImproperlyConfigured isn't raised when using RasterField": {
44+
# Normally RasterField.db_type() raises an error, but MongoDB
45+
# migrations don't need to call it, so the check doesn't happen.
46+
"gis_tests.gis_migrations.test_operations.NoRasterSupportTests",
47+
},
48+
"MongoDB doesn't support redundant spatial indexes.": {
49+
# Error: Index already exists with a different name
50+
"gis_tests.geoapp.test_indexes.SchemaIndexesTests.test_index_name",
51+
},
52+
"GIS lookups not supported.": {
53+
"gis_tests.geoapp.tests.GeoModelTest.test_gis_query_as_string",
54+
"gis_tests.geoapp.tests.GeoLookupTest.test_gis_lookups_with_complex_expressions",
55+
},
56+
"GeoJSONSerializer doesn't support ObjectId.": {
57+
"gis_tests.geoapp.test_serializers.GeoJSONSerializerTests.test_fields_option",
58+
"gis_tests.geoapp.test_serializers.GeoJSONSerializerTests.test_geometry_field_option",
59+
"gis_tests.geoapp.test_serializers.GeoJSONSerializerTests.test_serialization_base",
60+
"gis_tests.geoapp.test_serializers.GeoJSONSerializerTests.test_srid_option",
61+
},
62+
},
63+
)
64+
return skips

django_mongodb_backend/gis/lookups.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from django.contrib.gis.db.models.lookups import GISLookup
2+
from django.db import NotSupportedError
3+
4+
5+
def gis_lookup(self, compiler, connection): # noqa: ARG001
6+
raise NotSupportedError(f"MongoDB does not support the {self.lookup_name} lookup.")
7+
8+
9+
def register_lookups():
10+
GISLookup.as_mql = gis_lookup

0 commit comments

Comments
 (0)