Skip to content

Update to Django 5.2 #199

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

Merged
merged 5 commits into from
Apr 24, 2025
Merged
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
2 changes: 1 addition & 1 deletion .evergreen/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ python -m pip install -U pip
pip install -e .

# Install django and test dependencies
git clone --branch mongodb-5.1.x https://github.com/mongodb-forks/django django_repo
git clone --branch mongodb-5.2.x https://github.com/mongodb-forks/django django_repo
pushd django_repo/tests/
pip install -e ..
pip install -r requirements/py3.txt
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@v4
with:
repository: 'mongodb-forks/django'
ref: 'mongodb-5.1.x'
ref: 'mongodb-5.2.x'
path: 'django_repo'
persist-credentials: false
- name: Install system packages for Django's Python test dependencies
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ 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.1.x:
Django. For example, to get the latest compatible release for Django 5.2.x:
```bash
pip install --pre django-mongodb-backend==5.1.*
pip install --pre django-mongodb-backend==5.2.*
```
(Until the package is out of beta, you must use pip's `--pre` option.)

Expand All @@ -36,11 +36,11 @@ You can check what version of Django you're using with:
django-admin --version
```

The snippet below specifies `5.1.x.zip` at the end of
the template url to get the template for any Django version matching 5.1:
The snippet below specifies `5.2.x.zip` at the end of
the template url to get the template for any Django version matching 5.2:

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


Expand Down
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.1.0b3.dev0"
__version__ = "5.2.0b0.dev0"

# Check Django compatibility before other imports which may fail if the
# wrong version of Django is installed.
Expand Down
47 changes: 29 additions & 18 deletions django_mongodb_backend/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from django.utils.functional import cached_property
from pymongo import ASCENDING, DESCENDING

from .base import Cursor
from .query import MongoQuery, wrap_database_errors


Expand Down Expand Up @@ -403,12 +402,6 @@ def columns(self):
columns = (
self.get_default_columns(select_mask) if self.query.default_cols else self.query.select
)
# Populate QuerySet.select_related() data.
related_columns = []
if self.query.select_related:
self.get_related_selections(related_columns, select_mask)
if related_columns:
related_columns, _ = zip(*related_columns, strict=True)

annotation_idx = 1

Expand All @@ -427,11 +420,28 @@ def project_field(column):
annotation_idx += 1
return target, column

return (
tuple(map(project_field, columns))
+ tuple(self.annotations.items())
+ tuple(map(project_field, related_columns))
)
selected = []
if self.query.selected is None:
selected = [
*(project_field(col) for col in columns),
*self.annotations.items(),
]
else:
for expression in self.query.selected.values():
# Reference to an annotation.
if isinstance(expression, str):
alias, expression = expression, self.annotations[expression]
# Reference to a column.
elif isinstance(expression, int):
alias, expression = project_field(columns[expression])
selected.append((alias, expression))
# Populate QuerySet.select_related() data.
related_columns = []
if self.query.select_related:
self.get_related_selections(related_columns, select_mask)
if related_columns:
related_columns, _ = zip(*related_columns, strict=True)
return tuple(selected) + tuple(map(project_field, related_columns))

@cached_property
def base_table(self):
Expand Down Expand Up @@ -478,7 +488,11 @@ def get_combinator_queries(self):
# If the columns list is limited, then all combined queries
# must have the same columns list. Set the selects defined on
# the query on all combined queries, if not already set.
if not compiler_.query.values_select and self.query.values_select:
selected = self.query.selected
if selected is not None and compiler_.query.selected is None:
compiler_.query = compiler_.query.clone()
compiler_.query.set_values(selected)
elif not compiler_.query.values_select and self.query.values_select:
compiler_.query = compiler_.query.clone()
compiler_.query.set_values(
(
Expand Down Expand Up @@ -690,15 +704,12 @@ def collection_name(self):

class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):
def execute_sql(self, result_type=MULTI):
cursor = Cursor()
try:
query = self.build_query()
except EmptyResultSet:
rowcount = 0
return 0
else:
rowcount = query.delete()
cursor.rowcount = rowcount
return cursor
return query.delete()

def check_query(self):
super().check_query()
Expand Down
19 changes: 18 additions & 1 deletion django_mongodb_backend/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
from django.db.models.expressions import (
Case,
Col,
ColPairs,
CombinedExpression,
Exists,
ExpressionList,
ExpressionWrapper,
F,
NegatedExpression,
Expand All @@ -24,6 +26,8 @@
)
from django.db.models.sql import Query

from .query_utils import process_lhs


def case(self, compiler, connection):
case_parts = []
Expand Down Expand Up @@ -71,6 +75,13 @@ def col(self, compiler, connection): # noqa: ARG001
return f"${prefix}{self.target.column}"


def col_pairs(self, compiler, connection):
cols = self.get_cols()
if len(cols) > 1:
raise NotSupportedError("ColPairs is not supported.")
return cols[0].as_mql(compiler, connection)


def combined_expression(self, compiler, connection):
expressions = [
self.lhs.as_mql(compiler, connection),
Expand Down Expand Up @@ -150,7 +161,11 @@ def ref(self, compiler, connection): # noqa: ARG001
if isinstance(self.source, Col) and self.source.alias != compiler.collection_name
else ""
)
return f"${prefix}{self.refs}"
if hasattr(self, "ordinal"):
refs, _ = compiler.columns[self.ordinal - 1]
else:
refs = self.refs
return f"${prefix}{refs}"


def star(self, compiler, connection): # noqa: ARG001
Expand Down Expand Up @@ -200,8 +215,10 @@ def value(self, compiler, connection): # noqa: ARG001
def register_expressions():
Case.as_mql = case
Col.as_mql = col
ColPairs.as_mql = col_pairs
CombinedExpression.as_mql = combined_expression
Exists.as_mql = exists
ExpressionList.as_mql = process_lhs
ExpressionWrapper.as_mql = expression_wrapper
F.as_mql = f
NegatedExpression.as_mql = negated_expression
Expand Down
18 changes: 16 additions & 2 deletions django_mongodb_backend/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
greatest_least_ignores_nulls = True
has_json_object_function = False
has_native_json_field = True
rounds_to_even = True
supports_boolean_expr_in_select_clause = True
supports_collation_on_charfield = False
supports_column_check_constraints = False
Expand Down Expand Up @@ -56,8 +57,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
# Pattern lookups that use regexMatch don't work on JSONField:
# Unsupported conversion from array to string in $convert
"model_fields.test_jsonfield.TestQuerying.test_icontains",
# MongoDB gives ROUND(365, -1)=360 instead of 370 like other databases.
"db_functions.math.test_round.RoundTests.test_integer_with_negative_precision",
# Truncating in another timezone doesn't work becauase MongoDB converts
# the result back to UTC.
"db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_func_with_timezone",
Expand Down Expand Up @@ -88,6 +87,9 @@ class DatabaseFeatures(BaseDatabaseFeatures):
# of $setIsSubset must be arrays. Second argument is of type: null"
# https://jira.mongodb.org/browse/SERVER-99186
"model_fields_.test_arrayfield.QueryingTests.test_contained_by_subquery",
# Value.as_mql() doesn't call output_field.get_db_prep_save():
# https://github.com/mongodb/django-mongodb-backend/issues/282
"model_fields.test_jsonfield.TestSaveLoad.test_bulk_update_custom_get_prep_value",
}
# $bitAnd, #bitOr, and $bitXor are new in MongoDB 6.3.
_django_test_expected_failures_bitwise = {
Expand All @@ -112,6 +114,7 @@ def django_test_expected_failures(self):
# bson.errors.InvalidDocument: cannot encode object:
# <django.db.models.expressions.DatabaseDefault
"basic.tests.ModelInstanceCreationTests.test_save_primary_with_db_default",
"basic.tests.ModelInstanceCreationTests.test_save_primary_with_falsey_db_default",
"constraints.tests.UniqueConstraintTests.test_database_default",
"field_defaults.tests.DefaultTests",
"migrations.test_operations.OperationTests.test_add_field_both_defaults",
Expand Down Expand Up @@ -194,9 +197,13 @@ def django_test_expected_failures(self):
"prefetch_related.tests.Ticket21410Tests",
"queryset_pickle.tests.PickleabilityTestCase.test_pickle_prefetch_related_with_m2m_and_objects_deletion",
"serializers.test_json.JsonSerializerTestCase.test_serialize_prefetch_related_m2m",
"serializers.test_json.JsonSerializerTestCase.test_serialize_prefetch_related_m2m_with_natural_keys",
"serializers.test_jsonl.JsonlSerializerTestCase.test_serialize_prefetch_related_m2m",
"serializers.test_jsonl.JsonlSerializerTestCase.test_serialize_prefetch_related_m2m_with_natural_keys",
"serializers.test_xml.XmlSerializerTestCase.test_serialize_prefetch_related_m2m",
"serializers.test_xml.XmlSerializerTestCase.test_serialize_prefetch_related_m2m_with_natural_keys",
"serializers.test_yaml.YamlSerializerTestCase.test_serialize_prefetch_related_m2m",
"serializers.test_yaml.YamlSerializerTestCase.test_serialize_prefetch_related_m2m_with_natural_keys",
},
"AutoField not supported.": {
"bulk_create.tests.BulkCreateTests.test_bulk_insert_nullable_fields",
Expand Down Expand Up @@ -599,6 +606,13 @@ def django_test_expected_failures(self):
"foreign_object.tests.MultiColumnFKTests",
"foreign_object.tests.TestExtraJoinFilterQ",
},
"Tuple lookups are not supported.": {
"foreign_object.test_tuple_lookups.TupleLookupsTests",
},
"ColPairs is not supported.": {
# 'ColPairs' object has no attribute 'as_mql'
"auth_tests.test_views.CustomUserCompositePrimaryKeyPasswordResetTest",
},
"Custom lookups are not supported.": {
"custom_lookups.tests.BilateralTransformTests",
"custom_lookups.tests.LookupTests.test_basic_lookup",
Expand Down
6 changes: 1 addition & 5 deletions django_mongodb_backend/fields/array.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json

from django.core import checks, exceptions
from django.db.models import DecimalField, Field, Func, IntegerField, Transform, Value
from django.db.models import Field, Func, IntegerField, Transform, Value
from django.db.models.fields.mixins import CheckFieldDefaultMixin
from django.db.models.lookups import Exact, FieldGetDbPrepValueMixin, In, Lookup
from django.utils.translation import gettext_lazy as _
Expand Down Expand Up @@ -127,10 +127,6 @@ def db_type(self, connection):

def get_db_prep_value(self, value, connection, prepared=False):
if isinstance(value, list | tuple):
# Workaround for https://code.djangoproject.com/ticket/35982
# (fixed in Django 5.2).
if isinstance(self.base_field, DecimalField):
return [self.base_field.get_db_prep_save(i, connection) for i in value]
return [self.base_field.get_db_prep_value(i, connection, prepared=False) for i in value]
return value

Expand Down
2 changes: 2 additions & 0 deletions django_mongodb_backend/functions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.db import NotSupportedError
from django.db.models.expressions import Func
from django.db.models.functions import JSONArray
from django.db.models.functions.comparison import Cast, Coalesce, Greatest, Least, NullIf
from django.db.models.functions.datetime import (
Extract,
Expand Down Expand Up @@ -240,6 +241,7 @@ def register_functions():
Cot.as_mql = cot
Extract.as_mql = extract
Func.as_mql = func
JSONArray.as_mql = process_lhs
Left.as_mql = left
Length.as_mql = length
Log.as_mql = log
Expand Down
4 changes: 1 addition & 3 deletions django_mongodb_backend/lookups.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.db import NotSupportedError
from django.db.models.fields.related_lookups import In, MultiColSource, RelatedIn
from django.db.models.fields.related_lookups import In, RelatedIn
from django.db.models.lookups import (
BuiltinLookup,
FieldGetDbPrepValueIterableMixin,
Expand Down Expand Up @@ -34,8 +34,6 @@ def field_resolve_expression_parameter(self, compiler, connection, sql, param):


def in_(self, compiler, connection):
if isinstance(self.lhs, MultiColSource):
raise NotImplementedError("MultiColSource is not supported.")
db_rhs = getattr(self.rhs, "_db", None)
if db_rhs is not None and db_rhs != connection.alias:
raise ValueError(
Expand Down
22 changes: 0 additions & 22 deletions django_mongodb_backend/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,28 +195,6 @@ def execute_sql_flush(self, tables):
if not options.get("capped", False):
collection.delete_many({})

def prep_lookup_value(self, value, field, lookup):
"""
Perform type-conversion on `value` before using as a filter parameter.
"""
if getattr(field, "rel", None) is not None:
field = field.rel.get_related_field()
field_kind = field.get_internal_type()

if lookup in ("in", "range"):
return [
self._prep_lookup_value(subvalue, field, field_kind, lookup) for subvalue in value
]
return self._prep_lookup_value(value, field, field_kind, lookup)

def _prep_lookup_value(self, value, field, field_kind, lookup):
if value is None:
return None

if field_kind == "DecimalField":
value = self.adapt_decimalfield_value(value, field.max_digits, field.decimal_places)
return value

def explain_query_prefix(self, format=None, **options):
# Validate options.
validated_options = {}
Expand Down
5 changes: 1 addition & 4 deletions django_mongodb_backend/query_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@ def process_rhs(node, compiler, connection):
value = value[0]
if hasattr(node, "prep_lookup_value_mongo"):
value = node.prep_lookup_value_mongo(value)
# No need to prepare expressions like F() objects.
if hasattr(rhs, "resolve_expression"):
return value
return connection.ops.prep_lookup_value(value, node.lhs.output_field, node.lookup_name)
return value


def regex_match(field, regex_vals, insensitive=False):
Expand Down
4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@

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

version 5.1.x for Django 5.1.x
version 5.2.x for Django 5.2.x

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

Expand Down
8 changes: 4 additions & 4 deletions docs/source/intro/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ project template:

.. code-block:: bash

$ django-admin startproject mysite --template https://github.com/mongodb-labs/django-mongodb-project/archive/refs/heads/5.1.x.zip
$ django-admin startproject mysite --template https://github.com/mongodb-labs/django-mongodb-project/archive/refs/heads/5.2.x.zip

(If you're using a version of Django other than 5.1.x, replace the two numbers
(If you're using a version of Django other than 5.2.x, replace the two numbers
to match the first two numbers from your version.)

This template includes the following line in ``settings.py``::
Expand Down Expand Up @@ -95,9 +95,9 @@ includes this change:

.. code-block:: bash

$ python manage.py startapp myapp --template https://github.com/mongodb-labs/django-mongodb-app/archive/refs/heads/5.1.x.zip
$ python manage.py startapp myapp --template https://github.com/mongodb-labs/django-mongodb-app/archive/refs/heads/5.2.x.zip

(If you're using a version of Django other than 5.1.x, replace the two numbers
(If you're using a version of Django other than 5.2.x, replace the two numbers
to match the first two numbers from your version.)

.. _configuring-databases-setting:
Expand Down
Loading
Loading