Skip to content

Add GeoDjango support #308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
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
4 changes: 2 additions & 2 deletions .github/workflows/mongodb_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
else:
DATABASES = {
"default": {
"ENGINE": "django_mongodb_backend",
"ENGINE": "django_mongodb_backend_gis",
"NAME": "djangotests",
# Required when connecting to the Atlas image in Docker.
"OPTIONS": {"directConnection": True},
},
"other": {
"ENGINE": "django_mongodb_backend",
"ENGINE": "django_mongodb_backend_gis",
"NAME": "djangotests-other",
"OPTIONS": {"directConnection": True},
},
Expand Down
153 changes: 1 addition & 152 deletions .github/workflows/runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,158 +4,7 @@
import sys

test_apps = [
"admin_changelist",
"admin_checks",
"admin_custom_urls",
"admin_docs",
"admin_filters",
"admin_inlines",
"admin_ordering",
"admin_scripts",
"admin_utils",
"admin_views",
"admin_widgets",
"aggregation",
"aggregation_regress",
"annotations",
"apps",
"async",
"auth_tests",
"backends",
"basic",
"bulk_create",
"cache",
"check_framework",
"constraints",
"contenttypes_tests",
"context_processors",
"custom_columns",
"custom_lookups",
"custom_managers",
"custom_pk",
"datatypes",
"dates",
"datetimes",
"db_functions",
"defer",
"defer_regress",
"delete",
"delete_regress",
"empty",
"empty_models",
"expressions",
"expressions_case",
"field_defaults",
"file_storage",
"file_uploads",
"fixtures",
"fixtures_model_package",
"fixtures_regress",
"flatpages_tests",
"force_insert_update",
"foreign_object",
"forms_tests",
"from_db_value",
"generic_inline_admin",
"generic_relations",
"generic_relations_regress",
"generic_views",
"get_earliest_or_latest",
"get_object_or_404",
"get_or_create",
"i18n",
"indexes",
"inline_formsets",
"introspection",
"invalid_models_tests",
"known_related_objects",
"lookup",
"m2m_and_m2o",
"m2m_intermediary",
"m2m_multiple",
"m2m_recursive",
"m2m_regress",
"m2m_signals",
"m2m_through",
"m2m_through_regress",
"m2o_recursive",
"managers_regress",
"many_to_many",
"many_to_one",
"many_to_one_null",
"max_lengths",
"messages_tests",
"migrate_signals",
"migration_test_data_persistence",
"migrations",
"model_fields",
"model_forms",
"model_formsets",
"model_formsets_regress",
"model_indexes",
"model_inheritance",
"model_inheritance_regress",
"model_options",
"model_package",
"model_regress",
"model_utils",
"modeladmin",
"multiple_database",
"mutually_referential",
"nested_foreign_keys",
"null_fk",
"null_fk_ordering",
"null_queries",
"one_to_one",
"or_lookups",
"order_with_respect_to",
"ordering",
"pagination",
"prefetch_related",
"proxy_model_inheritance",
"proxy_models",
"queries",
"queryset_pickle",
"redirects_tests",
"reserved_names",
"reverse_lookup",
"save_delete_hooks",
"schema",
"select_for_update",
"select_related",
"select_related_onetoone",
"select_related_regress",
"serializers",
"servers",
"sessions_tests",
"shortcuts",
"signals",
"sitemaps_tests",
"sites_framework",
"sites_tests",
"string_lookup",
"swappable_models",
"syndication_tests",
"test_client",
"test_client_regress",
"test_runner",
"test_utils",
"timezones",
"transactions",
"unmanaged_models",
"update",
"update_only_fields",
"user_commands",
"validation",
"view_tests",
"xor_lookups",
# Add directories in django_mongodb_backend/tests
*sorted(
[
x.name
for x in (pathlib.Path(__file__).parent.parent.parent.resolve() / "tests").iterdir()
]
),
"gis_tests",
]
runtests = pathlib.Path(__file__).parent.resolve() / "runtests.py"
run_tests_cmd = f"python3 {runtests} %s --settings mongodb_settings -v 2"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-python-atlas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ jobs:
uses: actions/checkout@v4
with:
repository: 'mongodb-forks/django'
ref: 'mongodb-5.2.x'
ref: 'mongogis'
path: 'django_repo'
persist-credentials: false
- name: Install system packages for Django's Python test dependencies
run: |
sudo apt-get update
sudo apt-get install libmemcached-dev
sudo apt-get install gdal-bin libmemcached-dev
- name: Install Django and its Python test dependencies
run: |
cd django_repo/tests/
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ jobs:
uses: actions/checkout@v4
with:
repository: 'mongodb-forks/django'
ref: 'mongodb-5.2.x'
ref: 'mongogis'
path: 'django_repo'
persist-credentials: false
- name: Install system packages for Django's Python test dependencies
run: |
sudo apt-get update
sudo apt-get install libmemcached-dev
sudo apt-get install gdal-bin libmemcached-dev
- name: Install Django and its Python test dependencies
run: |
cd django_repo/tests/
Expand Down
4 changes: 2 additions & 2 deletions django_mongodb_backend/introspection.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from django.db.backends.base.introspection import BaseDatabaseIntrospection
from django.db.models import Index
from pymongo import ASCENDING, DESCENDING
from pymongo import ASCENDING, DESCENDING, GEOSPHERE

from django_mongodb_backend.indexes import SearchIndex, VectorSearchIndex


class DatabaseIntrospection(BaseDatabaseIntrospection):
ORDER_DIR = {ASCENDING: "ASC", DESCENDING: "DESC"}
ORDER_DIR = {ASCENDING: "ASC", DESCENDING: "DESC", GEOSPHERE: "GEO"}

def table_names(self, cursor=None, include_views=False):
return sorted([x["name"] for x in self.connection.database.list_collections()])
Expand Down
5 changes: 5 additions & 0 deletions django_mongodb_backend_gis/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .functions import register_functions
from .lookups import register_lookups

register_functions()
register_lookups()
21 changes: 21 additions & 0 deletions django_mongodb_backend_gis/adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import collections


class Adapter(collections.UserDict):
def __init__(self, obj, geography=False):
"""
Initialize on the spatial object.
"""
if obj.__class__.__name__ == "GeometryCollection":
self.data = {
"type": obj.__class__.__name__,
"geometries": [self.get_data(x) for x in obj],
}
else:
self.data = self.get_data(obj)

def get_data(self, obj):
return {
"type": obj.__class__.__name__,
"coordinates": obj.coords[0] if obj.__class__.__name__ == "Polygon" else obj.coords,
}
11 changes: 11 additions & 0 deletions django_mongodb_backend_gis/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django_mongodb_backend.base import DatabaseWrapper as BaseDatabaseWrapper

from .features import DatabaseFeatures
from .operations import DatabaseOperations
from .schema import DatabaseSchemaEditor


class DatabaseWrapper(BaseDatabaseWrapper):
SchemaEditorClass = DatabaseSchemaEditor
features_class = DatabaseFeatures
ops_class = DatabaseOperations
49 changes: 49 additions & 0 deletions django_mongodb_backend_gis/features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
from django.utils.functional import cached_property

from django_mongodb_backend.features import DatabaseFeatures as MongoFeatures


class DatabaseFeatures(BaseSpatialFeatures, MongoFeatures):
has_spatialrefsys_table = False
supports_transform = False

@cached_property
def django_test_expected_failures(self):
expected_failures = super().django_test_expected_failures
expected_failures.update(
{
# SRIDs aren't populated: AssertionError: 4326 != None
# self.assertEqual(4326, nullcity.point.srid)
"gis_tests.geoapp.tests.GeoModelTest.test_proxy",
# MongoDB does not support the within lookup
"gis_tests.relatedapp.tests.RelatedGeoModelTest.test06_f_expressions",
# 'Adapter' object has no attribute 'srid'
"gis_tests.geoapp.test_expressions.GeoExpressionsTests.test_geometry_value_annotation",
# Object of type ObjectId is not JSON serializable
"gis_tests.geoapp.test_serializers.GeoJSONSerializerTests.test_fields_option",
"gis_tests.geoapp.test_serializers.GeoJSONSerializerTests.test_geometry_field_option",
"gis_tests.geoapp.test_serializers.GeoJSONSerializerTests.test_serialization_base",
"gis_tests.geoapp.test_serializers.GeoJSONSerializerTests.test_srid_option",
# KeyError: 'within' connection.ops.gis_operators[self.lookup_name]
"gis_tests.geoapp.tests.GeoModelTest.test_gis_query_as_string",
# No lookups are supported (yet?)
"gis_tests.geoapp.tests.GeoLookupTest.test_gis_lookups_with_complex_expressions",
}
)
return expected_failures

@cached_property
def django_test_skips(self):
skips = super().django_test_skips
skips.update(
{
"inspectdb not supported.": {
"gis_tests.inspectapp.tests.InspectDbTests",
},
"Raw SQL not supported": {
"gis_tests.geoapp.tests.GeoModelTest.test_raw_sql_query",
},
},
)
return skips
6 changes: 6 additions & 0 deletions django_mongodb_backend_gis/functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Placeholder if we can support any functions.
# from django.contrib.gis.db.models.functions import Distance, Length, Perimeter


def register_functions():
pass
10 changes: 10 additions & 0 deletions django_mongodb_backend_gis/lookups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.contrib.gis.db.models.lookups import GISLookup
from django.db import NotSupportedError


def gis_lookup(self, compiler, connection): # noqa: ARG001
raise NotSupportedError(f"MongoDB does not support the {self.lookup_name} lookup.")


def register_lookups():
GISLookup.as_mql = gis_lookup
Loading