Skip to content
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 .evergreen/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
set -eux

# Install django-mongodb-backend
/opt/python/3.10/bin/python3 -m venv venv
/opt/python/3.12/bin/python3 -m venv venv
. venv/bin/activate
python -m pip install -U pip
pip install -e .

# Install django and test dependencies
git clone --branch mongodb-5.2.x https://github.com/mongodb-forks/django django_repo
git clone --branch mongodb-6.0.x https://github.com/mongodb-forks/django django_repo
pushd django_repo/tests/
pip install -e ..
pip install -r requirements/py3.txt
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/linters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: '3.10'
python-version: '3.12'
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
- name: Install Python dependencies
Expand All @@ -39,7 +39,7 @@ jobs:
with:
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
python-version: '3.10'
python-version: '3.12'
- name: Install dependencies
run: |
pip install -U pip
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-python-atlas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
uses: actions/checkout@v6
with:
repository: 'mongodb-forks/django'
ref: 'mongodb-5.2.x'
ref: 'mongodb-6.0.x'
path: 'django_repo'
persist-credentials: false
- name: Install system packages for Django's Python test dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-python-geo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
uses: actions/checkout@v6
with:
repository: 'mongodb-forks/django'
ref: 'mongodb-5.2.x'
ref: 'mongodb-6.0.x'
path: 'django_repo'
persist-credentials: false
- name: Install system packages for Django's Python test dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
uses: actions/checkout@v6
with:
repository: 'mongodb-forks/django'
ref: 'mongodb-5.2.x'
ref: 'mongodb-6.0.x'
path: 'django_repo'
persist-credentials: false
- name: Install system packages for Django's Python test dependencies
Expand Down
4 changes: 2 additions & 2 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ python:
- docs

build:
os: ubuntu-22.04
os: ubuntu-24.04
tools:
python: "3.11"
python: "3.12"
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,21 @@ https://django-mongodb-backend.readthedocs.io/en/latest/.
### Install

Use the version of `django-mongodb-backend` that corresponds to your version of
Django. For example, to get the latest compatible release for Django 5.2.x:
Django. For example, to get the latest compatible release for Django 6.0.x:

```bash
pip install django-mongodb-backend==5.2.*
pip install django-mongodb-backend==6.0.*
```

### Create a project

From your shell, run the following command to create a new Django project
called `example` using our project template. Make sure the end of the template
URL corresponds to your version of Django (e.g. `5.2.x.zip` for any Django
5.2.x version).
URL corresponds to your version of Django (e.g. `6.0.x.zip` for any Django
6.0.x version).

```bash
django-admin startproject example --template https://github.com/mongodb-labs/django-mongodb-project/archive/refs/heads/5.2.x.zip
django-admin startproject example --template https://github.com/mongodb-labs/django-mongodb-project/archive/refs/heads/6.0.x.zip
```

You can check what version of Django you're using with:
Expand Down
6 changes: 2 additions & 4 deletions django_mongodb_backend/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
__version__ = "5.2.4.dev0"
__version__ = "6.0.0b0.dev0"

# Check Django compatibility before other imports which may fail if the
# wrong version of Django is installed.
from .utils import check_django_compatability, parse_uri
from .utils import check_django_compatability

check_django_compatability()

Expand All @@ -15,8 +15,6 @@
from .lookups import register_lookups # noqa: E402
from .query import register_nodes # noqa: E402

__all__ = ["parse_uri"]

register_aggregates()
register_checks()
register_expressions()
Expand Down
28 changes: 25 additions & 3 deletions django_mongodb_backend/aggregates.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
from django.db.models.aggregates import Aggregate, Count, StdDev, Variance
from django.db.models.expressions import Case, Value, When
from django.db import NotSupportedError
from django.db.models.aggregates import (
Aggregate,
AggregateFilter,
Count,
StdDev,
StringAgg,
Variance,
)
from django.db.models.expressions import Case, Col, Value, When
from django.db.models.lookups import IsNull

from .query_utils import process_lhs
Expand All @@ -9,7 +17,11 @@


def aggregate(self, compiler, connection, operator=None, resolve_inner_expression=False):
if self.filter:
# TODO: isinstance(self.filter, Col) works around failure of
# aggregation.tests.AggregateTestCase.test_distinct_on_aggregate. Is this
# correct?
if self.filter is not None and not isinstance(self.filter, Col):
# Generate a CASE statement for this aggregate.
node = self.copy()
node.filter = None
source_expressions = node.get_source_expressions()
Expand All @@ -24,6 +36,10 @@ def aggregate(self, compiler, connection, operator=None, resolve_inner_expressio
return {f"${operator}": lhs_mql}


def aggregate_filter(self, compiler, connection):
return self.condition.as_mql(compiler, connection, as_expr=True)


def count(self, compiler, connection, resolve_inner_expression=False):
"""
When resolve_inner_expression=True, return the MQL that resolves as a
Expand Down Expand Up @@ -65,8 +81,14 @@ def stddev_variance(self, compiler, connection):
return aggregate(self, compiler, connection, operator=operator)


def string_agg(self, compiler, connection): # noqa: ARG001
raise NotSupportedError("StringAgg is not supported.")


def register_aggregates():
Aggregate.as_mql_expr = aggregate
AggregateFilter.as_mql_expr = aggregate_filter
Count.as_mql_expr = count
StdDev.as_mql_expr = stddev_variance
StringAgg.as_mql_expr = string_agg
Variance.as_mql_expr = stddev_variance
18 changes: 18 additions & 0 deletions django_mongodb_backend/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ class DatabaseFeatures(GISFeatures, BaseDatabaseFeatures):
"model_fields.test_jsonfield.TestSaveLoad.test_bulk_update_custom_get_prep_value",
# To debug: https://github.com/mongodb/django-mongodb-backend/issues/362
"constraints.tests.UniqueConstraintTests.test_validate_case_when",
# StringAgg is not supported.
"aggregation.tests.AggregateTestCase.test_distinct_on_stringagg",
"aggregation.tests.AggregateTestCase.test_string_agg_escapes_delimiter",
"aggregation.tests.AggregateTestCase.test_string_agg_filter",
"aggregation.tests.AggregateTestCase.test_string_agg_filter_in_subquery",
"aggregation.tests.AggregateTestCase.test_stringagg_default_value",
# bulk_create() population of _order not implemented.
# https://github.com/django/django/commit/953095d1e603fe0f8f01175b1409ca23818dcff9
"contenttypes_tests.test_order_with_respect_to.OrderWithRespectToGFKTests.test_bulk_create_allows_duplicate_order_values",
"contenttypes_tests.test_order_with_respect_to.OrderWithRespectToGFKTests.test_bulk_create_mixed_scenario",
"contenttypes_tests.test_order_with_respect_to.OrderWithRespectToGFKTests.test_bulk_create_respects_mixed_manual_order",
"contenttypes_tests.test_order_with_respect_to.OrderWithRespectToGFKTests.test_bulk_create_with_existing_children",
}
# $bitAnd, #bitOr, and $bitXor are new in MongoDB 6.3.
_django_test_expected_failures_bitwise = {
Expand Down Expand Up @@ -139,6 +151,7 @@ def django_test_expected_failures(self):
"validation.test_unique.PerformUniqueChecksTest.test_unique_db_default",
},
"Insert expressions aren't supported.": {
"basic.tests.ModelTest.test_save_expressions",
"bulk_create.tests.BulkCreateTests.test_bulk_insert_now",
"bulk_create.tests.BulkCreateTests.test_bulk_insert_expressions",
"expressions.tests.BasicExpressionsTests.test_new_object_create",
Expand Down Expand Up @@ -201,6 +214,7 @@ def django_test_expected_failures(self):
"prefetch_related.tests.LookupOrderingTest.test_order",
"prefetch_related.tests.MultiDbTests.test_using_is_honored_m2m",
"prefetch_related.tests.MultiTableInheritanceTest",
"prefetch_related.tests.PrefetchRelatedMTICacheTests",
"prefetch_related.tests.PrefetchRelatedTests",
"prefetch_related.tests.ReadPrefetchedObjectsCacheTests",
"prefetch_related.tests.Ticket21410Tests",
Expand Down Expand Up @@ -563,6 +577,7 @@ def django_test_expected_failures(self):
"Custom lookups are not supported.": {
"custom_lookups.tests.BilateralTransformTests",
"custom_lookups.tests.LookupTests.test_basic_lookup",
"custom_lookups.tests.LookupTests.test_custom_lookup_with_subquery",
"custom_lookups.tests.LookupTests.test_custom_name_lookup",
"custom_lookups.tests.LookupTests.test_div3_extract",
"custom_lookups.tests.SubqueryTransformTests.test_subquery_usage",
Expand All @@ -580,6 +595,9 @@ def django_test_expected_failures(self):
"test_utils.tests.DisallowedDatabaseQueriesTests.test_disallowed_database_queries",
"test_utils.tests.DisallowedDatabaseQueriesTests.test_disallowed_thread_database_connection",
},
"search lookup not supported on non-Atlas.": {
"expressions.tests.BasicExpressionsTests.test_lookups_subquery",
},
}

@cached_property
Expand Down
9 changes: 7 additions & 2 deletions django_mongodb_backend/fields/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
def valid_path_key_name(key_name):
# A lookup can use path syntax (field.subfield) unless it contains a dollar
# sign or period.
return not any(char in key_name for char in ("$", "."))
return not key_name.startswith("-") and not any(char in key_name for char in ("$", "."))


def build_json_mql_path(lhs, key_transforms, as_expr=False):
Expand All @@ -36,7 +36,12 @@ def build_json_mql_path(lhs, key_transforms, as_expr=False):
get_field = {"$getField": {"input": result, "field": key}}
# Handle array indexing if the key is a digit. If key is something
# like '001', it's not an array index despite isdigit() returning True.
if key.isdigit() and str(int(key)) == key:
try:
int(key)
is_digit = str(int(key)) == key
except ValueError:
is_digit = False
if is_digit:
result = {
"$cond": {
"if": {"$isArray": result},
Expand Down
2 changes: 2 additions & 0 deletions django_mongodb_backend/gis/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def gis_operators(self):
"FromWKT",
"GeoHash",
"GeometryDistance",
"GeometryType",
"Intersection",
"IsEmpty",
"IsValid",
Expand All @@ -52,6 +53,7 @@ def gis_operators(self):
"Perimeter",
"PointOnSurface",
"Reverse",
"Rotate",
"Scale",
"SnapToGrid",
"SymDifference",
Expand Down
47 changes: 0 additions & 47 deletions django_mongodb_backend/utils.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import copy
import time
import warnings

import django
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.db.backends.utils import logger
from django.utils.deprecation import RemovedInDjango60Warning
from django.utils.functional import SimpleLazyObject
from django.utils.text import format_lazy
from django.utils.version import get_version_tuple
from pymongo.uri_parser import parse_uri as pymongo_parse_uri


def check_django_compatability():
Expand All @@ -30,50 +27,6 @@ def check_django_compatability():
)


def parse_uri(uri, *, db_name=None, options=None, test=None):
"""
Convert the given uri into a dictionary suitable for Django's DATABASES
setting.
"""
warnings.warn(
'parse_uri() is deprecated. Put the connection string in DATABASES["HOST"] instead.',
RemovedInDjango60Warning,
stacklevel=2,
)
uri = pymongo_parse_uri(uri)
host = None
port = None
if uri["fqdn"]:
# This is a SRV URI and the host is the fqdn.
host = f"mongodb+srv://{uri['fqdn']}"
else:
nodelist = uri.get("nodelist")
if len(nodelist) == 1:
host, port = nodelist[0]
elif len(nodelist) > 1:
host = ",".join([f"{host}:{port}" for host, port in nodelist])
db_name = db_name or uri["database"]
if not db_name:
raise ImproperlyConfigured("You must provide the db_name parameter.")
opts = uri.get("options")
if options:
opts.update(options)
settings_dict = {
"ENGINE": "django_mongodb_backend",
"NAME": db_name,
"HOST": host,
"PORT": port,
"USER": uri.get("username"),
"PASSWORD": uri.get("password"),
"OPTIONS": opts,
}
if "authSource" not in settings_dict["OPTIONS"] and uri["database"]:
settings_dict["OPTIONS"]["authSource"] = uri["database"]
if test:
settings_dict["TEST"] = test
return settings_dict


def prefix_validation_error(error, prefix, code, params):
"""
Prefix a validation error message while maintaining the existing
Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@

intersphinx_mapping = {
"django": (
"https://docs.djangoproject.com/en/5.2/",
"https://docs.djangoproject.com/en/5.2/_objects/",
"https://docs.djangoproject.com/en/6.0/",
"https://docs.djangoproject.com/en/6.0/_objects/",
),
"mongodb": ("https://www.mongodb.com/docs/languages/python/django-mongodb/v5.2/", None),
"pymongo": ("https://www.mongodb.com/docs/languages/python/pymongo-driver/current/", None),
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Django MongoDB Backend
======================

version 5.2.x for Django 5.2.x
version 6.0.x for Django 6.0.x

.. rubric:: Everything you need to know about Django MongoDB Backend.

Expand Down
4 changes: 2 additions & 2 deletions docs/internals/contributing/unit-tests.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ Third, clone your fork and install it:
Next, get and install a copy of MongoDB's Django fork. This fork has some
test suite adaptions for Django MongoDB Backend. There is a branch for each
feature release of Django (e.g. ``mongodb-5.2.x`` below).
feature release of Django (e.g. ``mongodb-6.0.x`` below).

.. code-block:: bash
$ git clone https://github.com/mongodb-forks/django.git django-repo
$ cd django-repo
$ git checkout -t origin/mongodb-5.2.x
$ git checkout -t origin/mongodb-6.0.x
$ python -m pip install -e .
$ python -m pip install -r tests/requirements/py3.txt
Expand Down
Loading