Skip to content

Add support for AddEmbeddedIndex #16

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 34 commits into
base: mongodb-5.1.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2599172
disable JavaScript/Windows tests
timgraham Feb 11, 2025
fc328b4
use ObjectIdAutoField for contrib apps' default_auto_field
timgraham Apr 17, 2024
f6add51
Added DatabaseFeatures.supports_microsecond_precision.
timgraham May 29, 2024
b90cbbc
Fixed #35402 -- Fixed crash when DatabaseFeatures.django_test_skips r…
jonmcfee03 Apr 25, 2024
d1a6738
TruncQuarter not supported
timgraham Jun 1, 2024
b2d565e
edit assertion for MongoDB's even rounding
timgraham Jun 6, 2024
199d3a4
Updated JSONField's test_invalid_value.
timgraham Jun 28, 2024
b1858c2
Added regression tests for MongoDB $regexMatch pattern matching.
timgraham Jun 28, 2024
f28f02f
Update GenericForeignKey object_id to CharField/TextField
timgraham Jul 17, 2024
26a57c3
use ObjectIdAutoField in test models
timgraham Jul 17, 2024
d656a46
comment out usage of QuerySet.extra()
timgraham Jul 18, 2024
66c73ee
remove unsupported usage of nulls_first
timgraham Jul 22, 2024
f17e329
drop requirement that QuerySet.explain() log a query
timgraham Aug 16, 2024
c32c0b7
Refs #35042 -- Added missing skip to aggregation test.
timgraham Aug 20, 2024
1cd9060
aggregation, aggregation_regress edits
timgraham Aug 19, 2024
d32542d
Added supports_sequence_reset skip in backends tests.
timgraham Aug 23, 2024
96c9d61
schema and migrations test edits
timgraham Aug 24, 2024
35bc5c9
backends edits
timgraham Aug 29, 2024
c9acfc3
introspection test edits
timgraham Aug 27, 2024
270b6bd
remove SQL introspection from queries tests
timgraham Sep 3, 2024
887dd0c
Added QuerySet.union() test with renames.
WaVEV Sep 7, 2024
9ece473
Fixed #35815 -- Allowed db_default to be a literal.
timgraham Oct 9, 2024
dbe9eaf
edits for many test apps
timgraham Sep 26, 2024
9068733
indexes
timgraham Oct 14, 2024
8cbbe1b
fix "view on site" for non-integer pks
timgraham Oct 17, 2024
a420df1
allow runtests.py to discover tests in django_mongodb/tests
timgraham Oct 22, 2024
d9f1944
constraints edits for partial indexes
timgraham Nov 7, 2024
caa011a
fix test_model_admin_default_delete_action
timgraham Dec 14, 2024
ffaabfd
fix test_right_hand_division
timgraham Dec 16, 2024
2a65bed
Added missing test for QuerySet.delete() when raising EmptyResultSet.
timgraham Jan 3, 2025
5d13745
adapt tests for ObjectIdAutoField
timgraham Feb 11, 2025
f0fd411
Fixed #36201 -- Fixed `ModelChoiceField/ModelMultipleChoiceField.clea…
timgraham Feb 20, 2025
5ab8657
Fixed shortcut() crash on ObjectId pk
timgraham Feb 20, 2025
3323ca3
Add support for AddEmbeddedIndex
timgraham Mar 30, 2025
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
56 changes: 0 additions & 56 deletions .github/workflows/tests.yml

This file was deleted.

1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ answer newbie questions, and generally made Django that much better:
Jonathan Buchanan <[email protected]>
Jonathan Daugherty (cygnus) <http://www.cprogrammer.org/>
Jonathan Feignberg <[email protected]>
Jonathan McFee <[email protected]>
Jonathan Slenders
Jonny Park <[email protected]>
Jordan Bae <[email protected]>
Expand Down
2 changes: 1 addition & 1 deletion django/contrib/admin/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class SimpleAdminConfig(AppConfig):
"""Simple AppConfig which does not do automatic discovery."""

default_auto_field = "django.db.models.AutoField"
default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField"
default_site = "django.contrib.admin.sites.AdminSite"
name = "django.contrib.admin"
verbose_name = _("Administration")
Expand Down
2 changes: 1 addition & 1 deletion django/contrib/admin/sites.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ def wrapper(*args, **kwargs):
path("autocomplete/", wrap(self.autocomplete_view), name="autocomplete"),
path("jsi18n/", wrap(self.i18n_javascript, cacheable=True), name="jsi18n"),
path(
"r/<int:content_type_id>/<path:object_id>/",
"r/<str:content_type_id>/<path:object_id>/",
wrap(contenttype_views.shortcut),
name="view_on_site",
),
Expand Down
2 changes: 1 addition & 1 deletion django/contrib/auth/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


class AuthConfig(AppConfig):
default_auto_field = "django.db.models.AutoField"
default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField"
name = "django.contrib.auth"
verbose_name = _("Authentication and Authorization")

Expand Down
2 changes: 1 addition & 1 deletion django/contrib/contenttypes/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


class ContentTypesConfig(AppConfig):
default_auto_field = "django.db.models.AutoField"
default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField"
name = "django.contrib.contenttypes"
verbose_name = _("Content Types")

Expand Down
4 changes: 2 additions & 2 deletions django/contrib/contenttypes/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.apps import apps
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.http import Http404, HttpResponseRedirect
from django.utils.translation import gettext as _

Expand All @@ -19,7 +19,7 @@ def shortcut(request, content_type_id, object_id):
% {"ct_id": content_type_id}
)
obj = content_type.get_object_for_this_type(pk=object_id)
except (ObjectDoesNotExist, ValueError):
except (ObjectDoesNotExist, ValidationError, ValueError):
raise Http404(
_("Content type %(ct_id)s object %(obj_id)s doesn’t exist")
% {"ct_id": content_type_id, "obj_id": object_id}
Expand Down
2 changes: 1 addition & 1 deletion django/contrib/flatpages/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@


class FlatPagesConfig(AppConfig):
default_auto_field = "django.db.models.AutoField"
default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField"
name = "django.contrib.flatpages"
verbose_name = _("Flat Pages")
2 changes: 1 addition & 1 deletion django/contrib/gis/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


class GISConfig(AppConfig):
default_auto_field = "django.db.models.AutoField"
default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField"
name = "django.contrib.gis"
verbose_name = _("GIS")

Expand Down
2 changes: 1 addition & 1 deletion django/contrib/redirects/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@


class RedirectsConfig(AppConfig):
default_auto_field = "django.db.models.AutoField"
default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField"
name = "django.contrib.redirects"
verbose_name = _("Redirects")
2 changes: 1 addition & 1 deletion django/contrib/sites/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


class SitesConfig(AppConfig):
default_auto_field = "django.db.models.AutoField"
default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField"
name = "django.contrib.sites"
verbose_name = _("Sites")

Expand Down
4 changes: 3 additions & 1 deletion django/contrib/sites/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from django_mongodb_backend.fields import ObjectIdAutoField

import django.contrib.sites.models
from django.contrib.sites.models import _simple_domain_name_validator
from django.db import migrations, models
Expand All @@ -12,7 +14,7 @@ class Migration(migrations.Migration):
fields=[
(
"id",
models.AutoField(
ObjectIdAutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
Expand Down
11 changes: 9 additions & 2 deletions django/db/backends/base/creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,12 +347,19 @@ def mark_expected_failures_and_skips(self):
for reason, tests in self.connection.features.django_test_skips.items():
for test_name in tests:
test_case_name, _, test_method_name = test_name.rpartition(".")
if not test_method_name.startswith("test"):
test_case_name = test_name
test_method_name = None
test_app = test_name.split(".")[0]
# Importing a test app that isn't installed raises RuntimeError.
if test_app in settings.INSTALLED_APPS:
test_case = import_string(test_case_name)
test_method = getattr(test_case, test_method_name)
setattr(test_case, test_method_name, skip(reason)(test_method))
if test_method_name:
test_method = getattr(test_case, test_method_name)
setattr(test_case, test_method_name, skip(reason)(test_method))
else:
setattr(test_case, "__unittest_skip__", True)
setattr(test_case, "__unittest_skip_why__", reason)

def sql_table_creation_suffix(self):
"""
Expand Down
3 changes: 3 additions & 0 deletions django/db/backends/base/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ class BaseDatabaseFeatures:
# by returning the type used to store duration field?
supports_temporal_subtraction = False

# Do time/datetime fields have microsecond precision?
supports_microsecond_precision = True

# Does the __regex lookup support backreferencing and grouping?
supports_regex_backreferencing = True

Expand Down
106 changes: 105 additions & 1 deletion django/db/migrations/autodetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from graphlib import TopologicalSorter
from itertools import chain

from django_mongodb_backend.fields import EmbeddedModelField
from django_mongodb_backend.models import EMBEDDED

from django.conf import settings
from django.db import models
from django.db.migrations import operations
Expand Down Expand Up @@ -143,18 +146,23 @@ def _detect_changes(self, convert_apps=None, graph=None):
# resolve dependencies caused by M2Ms and FKs.
self.generated_operations = {}
self.altered_indexes = {}
self.altered_embedded_indexes = {}
self.altered_constraints = {}
self.renamed_fields = {}

# Prepare some old/new state and model lists, separating
# proxy models and ignoring unmigrated apps.
self.old_embedded_keys = set()
self.old_model_keys = set()
self.old_proxy_keys = set()
self.old_unmanaged_keys = set()
self.new_embedded_keys = set()
self.new_model_keys = set()
self.new_proxy_keys = set()
self.new_unmanaged_keys = set()
for (app_label, model_name), model_state in self.from_state.models.items():
if model_state.options.get("db_table") is EMBEDDED:
self.old_embedded_keys.add((app_label, model_name))
if not model_state.options.get("managed", True):
self.old_unmanaged_keys.add((app_label, model_name))
elif app_label not in self.from_state.real_apps:
Expand All @@ -164,7 +172,9 @@ def _detect_changes(self, convert_apps=None, graph=None):
self.old_model_keys.add((app_label, model_name))

for (app_label, model_name), model_state in self.to_state.models.items():
if not model_state.options.get("managed", True):
if model_state.options.get("db_table") is EMBEDDED:
self.new_embedded_keys.add((app_label, model_name))
elif not model_state.options.get("managed", True):
self.new_unmanaged_keys.add((app_label, model_name))
elif app_label not in self.from_state.real_apps or (
convert_apps and app_label in convert_apps
Expand Down Expand Up @@ -202,6 +212,7 @@ def _detect_changes(self, convert_apps=None, graph=None):
# This avoids the same computation in generate_removed_indexes()
# and generate_added_indexes().
self.create_altered_indexes()
self.create_altered_embedded_indexes()
self.create_altered_constraints()
# Generate index removal operations before field is removed
self.generate_removed_constraints()
Expand Down Expand Up @@ -1411,6 +1422,81 @@ def create_altered_indexes(self):
}
)

def create_altered_embedded_indexes(self, column_prefix=None, parent_model=None):
option_name = operations.AddEmbeddedIndex.option_name
for app_label, model_name in sorted(self.kept_model_keys):
# old_model_name = self.renamed_models.get(
# (app_label, model_name), model_name
# )
# old_parent_model_state = self.from_state.models[app_label, old_model_name]
new_parent_model_state = self.to_state.models[app_label, model_name]

for field_name in new_parent_model_state.fields:
field = new_parent_model_state.get_field(field_name)
if isinstance(field, EmbeddedModelField):
parent_model = new_parent_model_state.name
embedded_model = field.embedded_model
column_prefix = f"{field_name}."
embedded_model_name = embedded_model._meta.model_name

# TODO: handle renamed embedded models
old_model_state = self.from_state.models[
embedded_model._meta.app_label, embedded_model_name
]
new_model_state = self.to_state.models[
embedded_model._meta.app_label, embedded_model_name
]

old_indexes = old_model_state.options[option_name]
new_indexes = new_model_state.options[option_name]
added_indexes = [
idx for idx in new_indexes if idx not in old_indexes
]
# removed_indexes = [
# idx for idx in old_indexes if idx not in new_indexes
# ]
# renamed_indexes = []
# Find renamed indexes.
remove_from_added = []
# remove_from_removed = []
# for new_index in added_indexes:
# new_index_dec = new_index.deconstruct()
# new_index_name = new_index_dec[2].pop("name")
# for old_index in removed_indexes:
# old_index_dec = old_index.deconstruct()
# old_index_name = old_index_dec[2].pop("name")
# # Indexes are the same except for the names.
# if (
# new_index_dec == old_index_dec
# and new_index_name != old_index_name
# ):
# renamed_indexes.append((old_index_name, new_index_name, None)) # noqa [temp line length]
# remove_from_added.append(new_index)
# remove_from_removed.append(old_index)
# Remove renamed indexes from the lists of added and removed
# indexes.
added_indexes = [
idx for idx in added_indexes if idx not in remove_from_added
]
# removed_indexes = [
# idx for idx in removed_indexes if idx not in remove_from_removed # noqa [temp line length]
# ]

self.altered_embedded_indexes.update(
{
(
app_label,
embedded_model_name,
column_prefix,
parent_model,
): {
"added_indexes": added_indexes,
# "removed_indexes": removed_indexes,
# "renamed_indexes": renamed_indexes,
}
}
)

def generate_added_indexes(self):
for (app_label, model_name), alt_indexes in self.altered_indexes.items():
dependencies = self._get_dependencies_for_model(app_label, model_name)
Expand All @@ -1423,6 +1509,24 @@ def generate_added_indexes(self):
),
dependencies=dependencies,
)
for (
app_label,
model_name,
column_prefix,
parent_model_name,
), alt_indexes in self.altered_embedded_indexes.items():
dependencies = self._get_dependencies_for_model(app_label, model_name)
for index in alt_indexes["added_indexes"]:
self.add_operation(
app_label,
operations.AddEmbeddedIndex(
model_name=model_name,
index=index,
column_prefix=column_prefix,
parent_model_name=parent_model_name,
),
dependencies=dependencies,
)

def generate_removed_indexes(self):
for (app_label, model_name), alt_indexes in self.altered_indexes.items():
Expand Down
2 changes: 2 additions & 0 deletions django/db/migrations/operations/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .fields import AddField, AlterField, RemoveField, RenameField
from .models import (
AddConstraint,
AddEmbeddedIndex,
AddIndex,
AlterIndexTogether,
AlterModelManagers,
Expand Down Expand Up @@ -41,4 +42,5 @@
"RunPython",
"AlterOrderWithRespectTo",
"AlterModelManagers",
"AddEmbeddedIndex",
]
Loading
Loading