Skip to content

Update to Django 6.0 #280

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 6 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 .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@v5
with:
python-version: '3.10'
python-version: '3.12'
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
- name: Install Python dependencies
Expand All @@ -37,7 +37,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
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,7 +33,7 @@ jobs:
uses: actions/checkout@v4
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 All @@ -53,4 +53,4 @@ jobs:
working-directory: .
run: bash .github/workflows/start_local_atlas.sh mongodb/mongodb-atlas-local:7
- name: Run tests
run: python3 django_repo/tests/runtests_.py
run: python3 django_repo/tests/runtests.py --settings mongodb_settings -v 2
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@v4
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"
2 changes: 1 addition & 1 deletion django_mongodb_backend/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "5.2.0b2.dev0"
__version__ = "6.0.0b0.dev0"

# Check Django compatibility before other imports which may fail if the
# wrong version of Django is installed.
Expand Down
29 changes: 26 additions & 3 deletions django_mongodb_backend/aggregates.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
from django.db.models.aggregates import Aggregate, Count, StdDev, Variance
from django.db.models.expressions import Case, Value, When
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 @@ -16,7 +23,11 @@ def aggregate(
resolve_inner_expression=False,
**extra_context, # noqa: ARG001
):
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 @@ -31,6 +42,10 @@ def aggregate(
return {f"${operator}": lhs_mql}


def aggregate_filter(self, compiler, connection, **extra_context):
return self.condition.as_mql(compiler, connection, **extra_context)


def count(self, compiler, connection, resolve_inner_expression=False, **extra_context): # noqa: ARG001
"""
When resolve_inner_expression=True, return the MQL that resolves as a
Expand Down Expand Up @@ -72,8 +87,16 @@ def stddev_variance(self, compiler, connection, **extra_context):
return aggregate(self, compiler, connection, operator=operator, **extra_context)


def string_agg(self, compiler, connection, **extra_context): # # noqa: ARG001
from django.db import NotSupportedError

raise NotSupportedError("StringAgg is not supported.")


def register_aggregates():
Aggregate.as_mql = aggregate
AggregateFilter.as_mql = aggregate_filter
Count.as_mql = count
StdDev.as_mql = stddev_variance
StringAgg.as_mql = string_agg
Variance.as_mql = stddev_variance
67 changes: 63 additions & 4 deletions django_mongodb_backend/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import os

from django.core.exceptions import ImproperlyConfigured
from django.db import DEFAULT_DB_ALIAS
from django.db.backends.base.base import BaseDatabaseWrapper
from django.db.backends.utils import debug_transaction
from django.utils.asyncio import async_unsafe
from django.utils.functional import cached_property
from pymongo.collection import Collection
Expand Down Expand Up @@ -32,6 +34,17 @@ def __exit__(self, exception_type, exception_value, exception_traceback):
pass


def requires_transaction_support(func):
"""Make a method a no-op if transactions aren't supported."""

def wrapper(self, *args, **kwargs):
if not self.features.supports_transactions:
return
func(self, *args, **kwargs)

return wrapper


class DatabaseWrapper(BaseDatabaseWrapper):
data_types = {
"AutoField": "int",
Expand Down Expand Up @@ -140,6 +153,10 @@ def _isnull_operator(a, b):
ops_class = DatabaseOperations
validation_class = DatabaseValidation

def __init__(self, settings_dict, alias=DEFAULT_DB_ALIAS):
super().__init__(settings_dict, alias=alias)
self.session = None

def get_collection(self, name, **kwargs):
collection = Collection(self.database, name, **kwargs)
if self.queries_logged:
Expand Down Expand Up @@ -189,14 +206,48 @@ def _driver_info(self):
return DriverInfo("django-mongodb-backend", django_mongodb_backend_version)
return None

@requires_transaction_support
def _commit(self):
pass
if self.session:
with debug_transaction(self, "session.commit_transaction()"):
self.session.commit_transaction()
self._end_session()

@requires_transaction_support
def _rollback(self):
pass
if self.session:
with debug_transaction(self, "session.abort_transaction()"):
self.session.abort_transaction()
self._end_session()

def _start_transaction(self):
# Private API, specific to this backend.
if self.session is None:
self.session = self.connection.start_session()
with debug_transaction(self, "session.start_transaction()"):
self.session.start_transaction()

def set_autocommit(self, autocommit, force_begin_transaction_with_broken_autocommit=False):
self.autocommit = autocommit
def _end_session(self):
# Private API, specific to this backend.
self.session.end_session()
self.session = None

@requires_transaction_support
def _start_transaction_under_autocommit(self):
# Implementing this hook (intended only for SQLite), allows
# BaseDatabaseWrapper.set_autocommit() to use it to start a transaction
# rather than set_autocommit(), bypassing set_autocommit()'s call to
# debug_transaction(self, "BEGIN") which isn't semantic for a no-SQL
# backend.
self._start_transaction()

@requires_transaction_support
def _set_autocommit(self, autocommit, force_begin_transaction_with_broken_autocommit=False):
# Besides @transaction.atomic() (which uses
# _start_transaction_under_autocommit(), disabling autocommit is
# another way to start a transaction.
if not autocommit:
self._start_transaction()

def _close(self):
# Normally called by close(), this method is also called by some tests.
Expand All @@ -210,6 +261,10 @@ def close(self):

def close_pool(self):
"""Close the MongoClient."""
# Clear commit hooks and session.
self.run_on_commit = []
if self.session:
self._end_session()
connection = self.connection
if connection is None:
return
Expand All @@ -225,6 +280,10 @@ def close_pool(self):
def cursor(self):
return Cursor()

@requires_transaction_support
def validate_no_broken_transaction(self):
super().validate_no_broken_transaction()

def get_database_version(self):
"""Return a tuple of the database's version."""
return tuple(self.connection.server_info()["versionArray"])
4 changes: 2 additions & 2 deletions django_mongodb_backend/cache.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pickle
from datetime import datetime, timezone
from datetime import UTC, datetime

from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
from django.core.cache.backends.db import Options
Expand Down Expand Up @@ -72,7 +72,7 @@ def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT):
if timeout is None:
return datetime.max
timestamp = super().get_backend_timeout(timeout)
return datetime.fromtimestamp(timestamp, tz=timezone.utc)
return datetime.fromtimestamp(timestamp, tz=UTC)

def get(self, key, default=None, version=None):
return self.get_many([key], version).get(key, default)
Expand Down
10 changes: 8 additions & 2 deletions django_mongodb_backend/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,10 @@ def execute_sql(self, returning_fields=None):
@wrap_database_errors
def insert(self, docs, returning_fields=None):
"""Store a list of documents using field columns as element names."""
inserted_ids = self.collection.insert_many(docs).inserted_ids
self.connection.validate_no_broken_transaction()
inserted_ids = self.collection.insert_many(
docs, session=self.connection.session
).inserted_ids
return [(x,) for x in inserted_ids] if returning_fields else []

@cached_property
Expand Down Expand Up @@ -768,7 +771,10 @@ def execute_sql(self, result_type):

@wrap_database_errors
def update(self, criteria, pipeline):
return self.collection.update_many(criteria, pipeline).matched_count
self.connection.validate_no_broken_transaction()
return self.collection.update_many(
criteria, pipeline, session=self.connection.session
).matched_count

def check_query(self):
super().check_query()
Expand Down
Loading
Loading