From f34969811fafbb8d4277a59cefd3dd932712c250 Mon Sep 17 00:00:00 2001
From: "mongodb-dbx-release-bot[bot]"
 <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
Date: Thu, 30 Jan 2025 00:55:54 +0000
Subject: [PATCH 01/25] BUMP 5.0.0b0

Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
---
 django_mongodb_backend/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/django_mongodb_backend/__init__.py b/django_mongodb_backend/__init__.py
index 4810c363d..f647ec346 100644
--- a/django_mongodb_backend/__init__.py
+++ b/django_mongodb_backend/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "5.0.0a3"
+__version__ = "5.0.0b0"
 
 # Check Django compatibility before other imports which may fail if the
 # wrong version of Django is installed.

From 6fe433e3915d1a1b6f4fb14abb2de6598be7921e Mon Sep 17 00:00:00 2001
From: "mongodb-dbx-release-bot[bot]"
 <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
Date: Thu, 30 Jan 2025 00:59:27 +0000
Subject: [PATCH 02/25] BUMP 5.0.0b1.dev

Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
---
 django_mongodb_backend/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/django_mongodb_backend/__init__.py b/django_mongodb_backend/__init__.py
index f647ec346..f4b8fa405 100644
--- a/django_mongodb_backend/__init__.py
+++ b/django_mongodb_backend/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "5.0.0b0"
+__version__ = "5.0.0b1.dev0"
 
 # Check Django compatibility before other imports which may fail if the
 # wrong version of Django is installed.

From 68d4509865efe6d8fd6bf75fe1817705aa24ed46 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Thu, 30 Jan 2025 07:21:32 -0500
Subject: [PATCH 03/25] fix flaky test:
 model_fields_.test_embedded_model.ModelTests.test_pre_save

---
 tests/model_fields_/test_embedded_model.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/tests/model_fields_/test_embedded_model.py b/tests/model_fields_/test_embedded_model.py
index 4beb2e254..cd028cb3a 100644
--- a/tests/model_fields_/test_embedded_model.py
+++ b/tests/model_fields_/test_embedded_model.py
@@ -1,4 +1,5 @@
 import operator
+from datetime import timedelta
 
 from django.core.exceptions import FieldDoesNotExist, ValidationError
 from django.db import models
@@ -74,7 +75,9 @@ def test_pre_save(self):
         obj = Holder.objects.create(data=Data())
         auto_now = truncate_ms(obj.data.auto_now)
         auto_now_add = truncate_ms(obj.data.auto_now_add)
-        self.assertEqual(auto_now, auto_now_add)
+        # auto_now and auto_now_add may differ by a millisecond since they
+        # aren't generated simultaneously.
+        self.assertAlmostEqual(auto_now, auto_now_add, delta=timedelta(microseconds=1000))
         # save() updates auto_now but not auto_now_add.
         obj.save()
         self.assertEqual(truncate_ms(obj.data.auto_now_add), auto_now_add)

From 2662c75c7134dc5bd381eddeabb2ef8f86acdb8f Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Thu, 30 Jan 2025 07:22:52 -0500
Subject: [PATCH 04/25] correct a TestCase.setUpTestData() to be a classmethod

---
 tests/model_fields_/test_embedded_model.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tests/model_fields_/test_embedded_model.py b/tests/model_fields_/test_embedded_model.py
index cd028cb3a..eee0dd1a9 100644
--- a/tests/model_fields_/test_embedded_model.py
+++ b/tests/model_fields_/test_embedded_model.py
@@ -256,7 +256,8 @@ class MyModel(models.Model):
 
 
 class SubqueryExistsTests(TestCase):
-    def setUpTestData(self):
+    @classmethod
+    def setUpTestData(cls):
         address1 = Address(city="New York", state="NY", zip_code=10001)
         address2 = Address(city="Boston", state="MA", zip_code=20002)
         author1 = Author(name="Alice", age=30, address=address1)

From e5c73d7db13bdf55c1a2dbc626df36e16df17140 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Wed, 29 Jan 2025 21:26:42 -0500
Subject: [PATCH 05/25] fix MongoDB version in evergreen build name

An oversight in 4db618fdf9d9624795896bfbcfb762762c1566b6.
---
 .evergreen/config.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.evergreen/config.yml b/.evergreen/config.yml
index f0b010b84..d59bfe079 100644
--- a/.evergreen/config.yml
+++ b/.evergreen/config.yml
@@ -68,7 +68,7 @@ tasks:
       - func: "run unit tests"
 
 buildvariants:
-  - name: tests-5-noauth-nossl
+  - name: tests-6-noauth-nossl
     display_name: Run Tests 6.0 NoAuth NoSSL
     run_on: rhel87-small
     expansions:
@@ -79,7 +79,7 @@ buildvariants:
     tasks:
       - name: run-tests
 
-  - name: tests-5-auth-ssl
+  - name: tests-6-auth-ssl
     display_name: Run Tests 6.0 Auth SSL
     run_on: rhel87-small
     expansions:

From d4d18589771b439efffd3864d436fcc858862c39 Mon Sep 17 00:00:00 2001
From: Jib <jib.adegunloye@mongodb.com>
Date: Fri, 31 Jan 2025 13:16:41 -0500
Subject: [PATCH 06/25] Add Docs link to README (#244)

---
 README.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/README.md b/README.md
index cea132b0a..eea1063d0 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,12 @@ This backend is currently in development and is not advised for production workf
 changes may be made without notice. We welcome your feedback as we continue to
 explore and build. The best way to share this is via our [MongoDB Community Forum](https://www.mongodb.com/community/forums/tag/python).
 
+## Index
+* [Documentation](https://www.mongodb.com/docs/languages/python/django-mongodb/current/)
+* [Getting Started](https://www.mongodb.com/docs/languages/python/django-mongodb/current/get-started/)
+* [Model Your Data](https://www.mongodb.com/docs/languages/python/django-mongodb/current/model-data/)
+* [Limitations](https://www.mongodb.com/docs/languages/python/django-mongodb/current/limitations/)
+
 ## Install
 
 Use the version of `django-mongodb-backend` that corresponds to your version of

From 020a94cc74ac9f36cad0de09679a4bda11f83284 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Fri, 24 Jan 2025 20:27:11 -0500
Subject: [PATCH 07/25] add tests for nested embedded model form fields

---
 tests/model_forms_/forms.py               |  8 +-
 tests/model_forms_/models.py              | 10 +++
 tests/model_forms_/test_embedded_model.py | 92 ++++++++++++++++++++++-
 3 files changed, 107 insertions(+), 3 deletions(-)

diff --git a/tests/model_forms_/forms.py b/tests/model_forms_/forms.py
index 7bfed3fbb..1ac7b92a9 100644
--- a/tests/model_forms_/forms.py
+++ b/tests/model_forms_/forms.py
@@ -1,9 +1,15 @@
 from django import forms
 
-from .models import Author
+from .models import Author, Book
 
 
 class AuthorForm(forms.ModelForm):
     class Meta:
         fields = "__all__"
         model = Author
+
+
+class BookForm(forms.ModelForm):
+    class Meta:
+        fields = "__all__"
+        model = Book
diff --git a/tests/model_forms_/models.py b/tests/model_forms_/models.py
index df3bd580f..4e7cd0d6c 100644
--- a/tests/model_forms_/models.py
+++ b/tests/model_forms_/models.py
@@ -16,3 +16,13 @@ class Author(models.Model):
     age = models.IntegerField()
     address = EmbeddedModelField(Address)
     billing_address = EmbeddedModelField(Address, blank=True, null=True)
+
+
+class Publisher(EmbeddedModel):
+    name = models.CharField(max_length=50)
+    address = EmbeddedModelField(Address)
+
+
+class Book(models.Model):
+    title = models.CharField(max_length=50)
+    publisher = EmbeddedModelField(Publisher)
diff --git a/tests/model_forms_/test_embedded_model.py b/tests/model_forms_/test_embedded_model.py
index 240f8c6d8..d7ab5c4fe 100644
--- a/tests/model_forms_/test_embedded_model.py
+++ b/tests/model_forms_/test_embedded_model.py
@@ -1,7 +1,7 @@
 from django.test import TestCase
 
-from .forms import AuthorForm
-from .models import Address, Author
+from .forms import AuthorForm, BookForm
+from .models import Address, Author, Book, Publisher
 
 
 class ModelFormTests(TestCase):
@@ -128,3 +128,91 @@ def test_rendering(self):
                 <input type="number" name="address-zip_code" required id="id_address-zip_code">
             </div>""",
         )
+
+
+class NestedFormTests(TestCase):
+    def test_update(self):
+        book = Book.objects.create(
+            title="Learning MongoDB",
+            publisher=Publisher(
+                name="Random House", address=Address(city="NYC", state="NY", zip_code="10001")
+            ),
+        )
+        data = {
+            "title": "Learning MongoDB!",
+            "publisher-name": "Random House!",
+            "publisher-address-po_box": "",
+            "publisher-address-city": "New York City",
+            "publisher-address-state": "NY",
+            "publisher-address-zip_code": "10001",
+        }
+        form = BookForm(data, instance=book)
+        self.assertTrue(form.is_valid())
+        form.save()
+        book.refresh_from_db()
+        self.assertEqual(book.title, "Learning MongoDB!")
+        self.assertEqual(book.publisher.name, "Random House!")
+        self.assertEqual(book.publisher.address.city, "New York City")
+
+    def test_some_missing_data(self):
+        """A required field (zip_code) is missing."""
+        book = Book.objects.create(
+            title="Learning MongoDB",
+            publisher=Publisher(
+                name="Random House", address=Address(city="NYC", state="NY", zip_code="10001")
+            ),
+        )
+        data = {
+            "title": "Learning MongoDB!",
+            "publisher-name": "Random House!",
+            "publisher-address-po_box": "",
+            "publisher-address-city": "New York City",
+            "publisher-address-state": "NY",
+            "publisher-address-zip_code": "",
+        }
+        form = BookForm(data, instance=book)
+        self.assertFalse(form.is_valid())
+        self.assertEqual(form.errors["publisher"], ["Enter all required values."])
+
+    def test_invalid_field_data(self):
+        """A field's data (state) is too long."""
+        book = Book.objects.create(
+            title="Learning MongoDB",
+            publisher=Publisher(
+                name="Random House", address=Address(city="NYC", state="NY", zip_code="10001")
+            ),
+        )
+        data = {
+            "title": "Learning MongoDB!",
+            "publisher-name": "Random House!",
+            "publisher-address-po_box": "",
+            "publisher-address-city": "New York City",
+            "publisher-address-state": "TOO LONG",
+            "publisher-address-zip_code": "10001",
+        }
+        form = BookForm(data, instance=book)
+        self.assertFalse(form.is_valid())
+        self.assertEqual(
+            form.errors["publisher"],
+            ["Ensure this value has at most 2 characters (it has 8)."],
+        )
+
+    def test_all_missing_data(self):
+        """An embedded model with all data missing triggers a required error."""
+        book = Book.objects.create(
+            title="Learning MongoDB",
+            publisher=Publisher(
+                name="Random House", address=Address(city="NYC", state="NY", zip_code="10001")
+            ),
+        )
+        data = {
+            "title": "Learning MongoDB!",
+            "publisher-name": "Random House!",
+            "publisher-address-po_box": "",
+            "publisher-address-city": "",
+            "publisher-address-state": "",
+            "publisher-address-zip_code": "",
+        }
+        form = BookForm(data, instance=book)
+        self.assertFalse(form.is_valid())
+        self.assertEqual(form.errors["publisher"], ["This field is required."])

From 288fe24b151c360babd392a9dff5140ed3c9c6ba Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Fri, 24 Jan 2025 20:31:01 -0500
Subject: [PATCH 08/25] fix form element names of nested embedded model fields

---
 .../forms/fields/embedded_model.py            | 12 +++++-
 tests/model_forms_/test_embedded_model.py     | 37 +++++++++++++++++++
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/django_mongodb_backend/forms/fields/embedded_model.py b/django_mongodb_backend/forms/fields/embedded_model.py
index b86e85e78..552008181 100644
--- a/django_mongodb_backend/forms/fields/embedded_model.py
+++ b/django_mongodb_backend/forms/fields/embedded_model.py
@@ -20,9 +20,17 @@ def decompress(self, value):
 
 
 class EmbeddedModelBoundField(forms.BoundField):
+    def __init__(self, form, field, name, prefix_override=None):
+        super().__init__(form, field, name)
+        # prefix_override overrides the prefix in self.field.form_kwargs so
+        # that nested embedded model form elements have the correct name.
+        self.prefix_override = prefix_override
+
     def __str__(self):
         """Render the model form as the representation for this field."""
         form = self.field.model_form_cls(instance=self.value(), **self.field.form_kwargs)
+        if self.prefix_override:
+            form.prefix = self.prefix_override
         return mark_safe(f"{form.as_div()}")  # noqa: S308
 
 
@@ -53,7 +61,9 @@ def compress(self, data_dict):
         return self.model_form._meta.model(**values)
 
     def get_bound_field(self, form, field_name):
-        return EmbeddedModelBoundField(form, self, field_name)
+        # Nested embedded model form fields need a double prefix.
+        prefix_override = f"{form.prefix}-{self.model_form.prefix}" if form.prefix else None
+        return EmbeddedModelBoundField(form, self, field_name, prefix_override)
 
     def bound_data(self, data, initial):
         if self.disabled:
diff --git a/tests/model_forms_/test_embedded_model.py b/tests/model_forms_/test_embedded_model.py
index d7ab5c4fe..f53865851 100644
--- a/tests/model_forms_/test_embedded_model.py
+++ b/tests/model_forms_/test_embedded_model.py
@@ -216,3 +216,40 @@ def test_all_missing_data(self):
         form = BookForm(data, instance=book)
         self.assertFalse(form.is_valid())
         self.assertEqual(form.errors["publisher"], ["This field is required."])
+
+    def test_rendering(self):
+        form = BookForm()
+        self.assertHTMLEqual(
+            str(form.fields["publisher"].get_bound_field(form, "publisher")),
+            """
+            <div>
+              <label for="id_publisher-name">Name:</label>
+              <input type="text" name="publisher-name" maxlength="50"
+                required id="id_publisher-name">
+            </div>
+            <div>
+              <fieldset>
+                <legend>Address:</legend>
+                <div>
+                  <label for="id_publisher-address-po_box">PO Box:</label>
+                  <input type="text" name="publisher-address-po_box" maxlength="50"
+                    id="id_publisher-address-po_box">
+                </div>
+                <div>
+                  <label for="id_publisher-address-city">City:</label>
+                  <input type="text" name="publisher-address-city" maxlength="20"
+                    required id="id_publisher-address-city">
+                </div>
+                <div>
+                  <label for="id_publisher-address-state">State:</label>
+                  <input type="text" name="publisher-address-state" maxlength="2"
+                    required id="id_publisher-address-state">
+                </div>
+                <div>
+                  <label for="id_publisher-address-zip_code">Zip code:</label>
+                  <input type="number" name="publisher-address-zip_code"
+                    required id="id_publisher-address-zip_code">
+                </div>
+              </fieldset>
+            </div>""",
+        )

From 556ecf6b45f03cb1fb54a2abcc80dcbd87b85b17 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Wed, 29 Jan 2025 09:57:42 -0500
Subject: [PATCH 09/25] fix crash when rendering an invalid form with nested
 embedded model fields

---
 .../forms/fields/embedded_model.py            |  9 ++
 tests/model_forms_/test_embedded_model.py     | 94 +++++++++++++++++++
 2 files changed, 103 insertions(+)

diff --git a/django_mongodb_backend/forms/fields/embedded_model.py b/django_mongodb_backend/forms/fields/embedded_model.py
index 552008181..bbfa9c02c 100644
--- a/django_mongodb_backend/forms/fields/embedded_model.py
+++ b/django_mongodb_backend/forms/fields/embedded_model.py
@@ -70,3 +70,12 @@ def bound_data(self, data, initial):
             return initial
         # Transform the bound data into a model instance.
         return self.compress(data)
+
+    def prepare_value(self, value):
+        # When rendering a form with errors, nested EmbeddedModelField data
+        # won't be compressed if MultiValueField.clean() raises ValidationError
+        # error before compress() is called. The data must be compressed here
+        # so that EmbeddedModelBoundField.value() returns a model instance
+        # (rather than a list) for initializing the form in
+        # EmbeddedModelBoundField.__str__().
+        return self.compress(value) if isinstance(value, list) else value
diff --git a/tests/model_forms_/test_embedded_model.py b/tests/model_forms_/test_embedded_model.py
index f53865851..4447b59f2 100644
--- a/tests/model_forms_/test_embedded_model.py
+++ b/tests/model_forms_/test_embedded_model.py
@@ -173,6 +173,53 @@ def test_some_missing_data(self):
         form = BookForm(data, instance=book)
         self.assertFalse(form.is_valid())
         self.assertEqual(form.errors["publisher"], ["Enter all required values."])
+        self.assertHTMLEqual(
+            str(form),
+            """
+            <div>
+              <label for="id_title">Title:</label>
+              <input type="text" name="title" value="Learning MongoDB!" maxlength="50"
+                required id="id_title">
+            </div>
+            <div>
+              <fieldset>
+                <legend>Publisher:</legend>
+                <ul class="errorlist">
+                  <li>Enter all required values.</li>
+                </ul>
+                <div>
+                  <label for="id_publisher-name">Name:</label>
+                  <input type="text" name="publisher-name" value="Random House!" maxlength="50"
+                    required id="id_publisher-name">
+                </div>
+                <div>
+                  <fieldset>
+                    <legend>Address:</legend>
+                    <div>
+                      <label for="id_publisher-address-po_box">PO Box:</label>
+                      <input type="text" name="publisher-address-po_box" maxlength="50"
+                        id="id_publisher-address-po_box">
+                    </div>
+                    <div>
+                      <label for="id_publisher-address-city">City:</label>
+                      <input type="text" name="publisher-address-city" value="New York City"
+                        maxlength="20" required id="id_publisher-address-city">
+                    </div>
+                    <div>
+                      <label for="id_publisher-address-state">State:</label>
+                      <input type="text" name="publisher-address-state" value="NY"
+                        maxlength="2" required id="id_publisher-address-state">
+                    </div>
+                    <div>
+                      <label for="id_publisher-address-zip_code">Zip code:</label>
+                      <input type="number" name="publisher-address-zip_code"
+                        required id="id_publisher-address-zip_code">
+                    </div>
+                  </fieldset>
+                </div>
+              </fieldset>
+            </div>""",
+        )
 
     def test_invalid_field_data(self):
         """A field's data (state) is too long."""
@@ -196,6 +243,53 @@ def test_invalid_field_data(self):
             form.errors["publisher"],
             ["Ensure this value has at most 2 characters (it has 8)."],
         )
+        self.assertHTMLEqual(
+            str(form),
+            """
+            <div>
+              <label for="id_title">Title:</label>
+              <input type="text" name="title" value="Learning MongoDB!"
+                maxlength="50" required id="id_title">
+            </div>
+            <div>
+              <fieldset>
+                <legend>Publisher:</legend>
+                <ul class="errorlist">
+                  <li>Ensure this value has at most 2 characters (it has 8).</li>
+                </ul>
+                <div>
+                  <label for="id_publisher-name">Name:</label>
+                  <input type="text" name="publisher-name" value="Random House!"
+                    maxlength="50" required id="id_publisher-name">
+                </div>
+                <div>
+                  <fieldset>
+                    <legend>Address:</legend>
+                    <div>
+                      <label for="id_publisher-address-po_box">PO Box:</label>
+                      <input type="text" name="publisher-address-po_box"
+                        maxlength="50" id="id_publisher-address-po_box">
+                    </div>
+                    <div>
+                      <label for="id_publisher-address-city">City:</label>
+                      <input type="text" name="publisher-address-city" value="New York City"
+                        maxlength="20" required id="id_publisher-address-city">
+                    </div>
+                    <div>
+                      <label for="id_publisher-address-state">State:</label>
+                      <input type="text" name="publisher-address-state" value="TOO LONG"
+                        maxlength="2" required id="id_publisher-address-state">
+                    </div>
+                    <div>
+                      <label for="id_publisher-address-zip_code">Zip code:</label>
+                      <input type="number" name="publisher-address-zip_code" value="10001"
+                        required id="id_publisher-address-zip_code">
+                    </div>
+                  </fieldset>
+                </div>
+              </fieldset>
+            </div>""",
+        )
 
     def test_all_missing_data(self):
         """An embedded model with all data missing triggers a required error."""

From 0556474afee4ed60bebcf1d46b95d9fe30a3dec7 Mon Sep 17 00:00:00 2001
From: Jib <jib.adegunloye@mongodb.com>
Date: Mon, 3 Feb 2025 16:00:01 -0500
Subject: [PATCH 10/25] Update the limitations link to new (#248)

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index eea1063d0..99c22ab86 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ explore and build. The best way to share this is via our [MongoDB Community Foru
 * [Documentation](https://www.mongodb.com/docs/languages/python/django-mongodb/current/)
 * [Getting Started](https://www.mongodb.com/docs/languages/python/django-mongodb/current/get-started/)
 * [Model Your Data](https://www.mongodb.com/docs/languages/python/django-mongodb/current/model-data/)
-* [Limitations](https://www.mongodb.com/docs/languages/python/django-mongodb/current/limitations/)
+* [Limitations & Future Work](https://www.mongodb.com/docs/languages/python/django-mongodb/current/limitations-upcoming/)
 
 ## Install
 

From bfb8711f7cc68ffcc1f6415a68531f638ab9e66a Mon Sep 17 00:00:00 2001
From: Jib <jib.adegunloye@mongodb.com>
Date: Mon, 3 Feb 2025 16:00:17 -0500
Subject: [PATCH 11/25] provide django version check guidance (#245)

---
 README.md | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 99c22ab86..5d2700ab7 100644
--- a/README.md
+++ b/README.md
@@ -26,8 +26,14 @@ $ pip install --pre django-mongodb-backend==5.0.*
 
 From your shell, run the following command to create a new Django project
 called `example` using our custom template. Make sure the zipfile referenced
-at the end of the template link corresponds to your
-version of Django. The snippet below specifies `5.0.x.zip` at the end of
+at the end of the template link corresponds to your version of Django.
+
+You can check what version of Django you're using with:
+```bash
+$ django-admin --version
+```
+
+The snippet below specifies `5.0.x.zip` at the end of
 the template url to get the template for any Django version matching 5.0:
 
 ```bash

From 1656664188a998f84a800eb05337eae378597bd6 Mon Sep 17 00:00:00 2001
From: Steven Silvester <steve.silvester@mongodb.com>
Date: Thu, 6 Feb 2025 13:44:14 -0600
Subject: [PATCH 12/25] PYTHON-5047 Do not run nightly release check on forks
 (#251)

---
 .github/workflows/release-python.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml
index 206076bc4..61021dc53 100644
--- a/.github/workflows/release-python.yml
+++ b/.github/workflows/release-python.yml
@@ -37,6 +37,7 @@ jobs:
   pre-publish:
     environment: release
     runs-on: ubuntu-latest
+    if: github.repository_owner == 'mongodb' || github.event_name == 'workflow_dispatch'
     permissions:
       id-token: write
       contents: write

From 515173c7379b8d23b2325edb55faab62d25a6d63 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Thu, 6 Feb 2025 11:32:09 -0500
Subject: [PATCH 13/25] Add @async_unsafe to DatabaseWrapper methods as needed

---
 django_mongodb_backend/base.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/django_mongodb_backend/base.py b/django_mongodb_backend/base.py
index a052a927b..d6b1d67cb 100644
--- a/django_mongodb_backend/base.py
+++ b/django_mongodb_backend/base.py
@@ -3,6 +3,7 @@
 
 from django.core.exceptions import ImproperlyConfigured
 from django.db.backends.base.base import BaseDatabaseWrapper
+from django.utils.asyncio import async_unsafe
 from pymongo.collection import Collection
 from pymongo.driver_info import DriverInfo
 from pymongo.mongo_client import MongoClient
@@ -172,6 +173,7 @@ def get_connection_params(self):
             **settings_dict["OPTIONS"],
         }
 
+    @async_unsafe
     def get_new_connection(self, conn_params):
         return MongoClient(**conn_params, driver=self._driver_info())
 
@@ -189,11 +191,13 @@ def _rollback(self):
     def set_autocommit(self, autocommit, force_begin_transaction_with_broken_autocommit=False):
         pass
 
+    @async_unsafe
     def close(self):
         super().close()
         with contextlib.suppress(AttributeError):
             del self.database
 
+    @async_unsafe
     def cursor(self):
         return Cursor()
 

From 74f7be2f2739611d9395e470125bd377e1aa674d Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Wed, 5 Feb 2025 19:03:45 -0500
Subject: [PATCH 14/25] fix persistent connections

Without autocommit set, BaseDatabaseWrapper.close_if_unusable_or_obsolete()
short-circuits early and always closes the connection after each request.
---
 django_mongodb_backend/base.py |  2 +-
 tests/backend_/test_base.py    | 11 ++++++++++-
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/django_mongodb_backend/base.py b/django_mongodb_backend/base.py
index d6b1d67cb..e28fdc26b 100644
--- a/django_mongodb_backend/base.py
+++ b/django_mongodb_backend/base.py
@@ -189,7 +189,7 @@ def _rollback(self):
         pass
 
     def set_autocommit(self, autocommit, force_begin_transaction_with_broken_autocommit=False):
-        pass
+        self.autocommit = autocommit
 
     @async_unsafe
     def close(self):
diff --git a/tests/backend_/test_base.py b/tests/backend_/test_base.py
index 689c6dde7..2ce48bbeb 100644
--- a/tests/backend_/test_base.py
+++ b/tests/backend_/test_base.py
@@ -1,6 +1,6 @@
 from django.core.exceptions import ImproperlyConfigured
 from django.db import connection
-from django.test import SimpleTestCase
+from django.test import SimpleTestCase, TestCase
 
 from django_mongodb_backend.base import DatabaseWrapper
 
@@ -12,3 +12,12 @@ def test_database_name_empty(self):
         msg = 'settings.DATABASES is missing the "NAME" value.'
         with self.assertRaisesMessage(ImproperlyConfigured, msg):
             DatabaseWrapper(settings).get_connection_params()
+
+
+class DatabaseWrapperConnectionTests(TestCase):
+    def test_set_autocommit(self):
+        self.assertIs(connection.get_autocommit(), True)
+        connection.set_autocommit(False)
+        self.assertIs(connection.get_autocommit(), False)
+        connection.set_autocommit(True)
+        self.assertIs(connection.get_autocommit(), True)

From 7c544108a6414cfcb17d7cd18b26f5d4ced3e38b Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Wed, 22 Jan 2025 21:51:17 -0500
Subject: [PATCH 15/25] =?UTF-8?q?Restructure=20docs=20per=20Di=C3=A1taxis?=
 =?UTF-8?q?=20framework?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add intro, reference, and topic sections.
---
 README.md                                    |   3 +
 docs/source/conf.py                          |   9 +-
 docs/source/contents.rst                     |  23 +++
 docs/source/faq.rst                          |  34 ++++
 docs/source/index.rst                        |  59 +++++--
 docs/source/internals.rst                    |  25 +++
 docs/source/intro/configure.rst              | 157 +++++++++++++++++++
 docs/source/intro/index.rst                  |  26 +++
 docs/source/intro/install.rst                |  17 ++
 docs/source/{ => ref}/forms.rst              |   0
 docs/source/ref/index.rst                    |  10 ++
 docs/source/{ => ref/models}/fields.rst      |  10 +-
 docs/source/ref/models/index.rst             |  12 ++
 docs/source/{ => ref/models}/models.rst      |   4 +-
 docs/source/{ => ref/models}/querysets.rst   |  37 +++++
 docs/source/ref/utils.rst                    |  38 +++++
 docs/source/{ => topics}/embedded-models.rst |   0
 docs/source/topics/index.rst                 |  12 ++
 docs/source/topics/known-issues.rst          | 102 ++++++++++++
 pyproject.toml                               |   3 +
 20 files changed, 560 insertions(+), 21 deletions(-)
 create mode 100644 docs/source/contents.rst
 create mode 100644 docs/source/faq.rst
 create mode 100644 docs/source/internals.rst
 create mode 100644 docs/source/intro/configure.rst
 create mode 100644 docs/source/intro/index.rst
 create mode 100644 docs/source/intro/install.rst
 rename docs/source/{ => ref}/forms.rst (100%)
 create mode 100644 docs/source/ref/index.rst
 rename docs/source/{ => ref/models}/fields.rst (97%)
 create mode 100644 docs/source/ref/models/index.rst
 rename docs/source/{ => ref/models}/models.rst (81%)
 rename docs/source/{ => ref/models}/querysets.rst (63%)
 create mode 100644 docs/source/ref/utils.rst
 rename docs/source/{ => topics}/embedded-models.rst (100%)
 create mode 100644 docs/source/topics/index.rst
 create mode 100644 docs/source/topics/known-issues.rst

diff --git a/README.md b/README.md
index 5d2700ab7..63691e61d 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,9 @@ explore and build. The best way to share this is via our [MongoDB Community Foru
 * [Model Your Data](https://www.mongodb.com/docs/languages/python/django-mongodb/current/model-data/)
 * [Limitations & Future Work](https://www.mongodb.com/docs/languages/python/django-mongodb/current/limitations-upcoming/)
 
+The documentation in the "docs" directory is online at
+https://django-mongodb-backend.readthedocs.io/en/latest/.
+
 ## Install
 
 Use the version of `django-mongodb-backend` that corresponds to your version of
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 55bb84e14..183c4bbf2 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -16,8 +16,8 @@
 # documentation root, use os.path.abspath to make it absolute, like shown here.
 sys.path.append(str((Path(__file__).parent / "_ext").resolve()))
 
-project = "django_mongodb_backend"
-copyright = "2024, The MongoDB Python Team"
+project = "Django MongoDB Backend"
+copyright = "2025, The MongoDB Python Team"
 author = "The MongoDB Python Team"
 release = _version("django_mongodb_backend")
 
@@ -39,12 +39,15 @@
 intersphinx_mapping = {
     "django": (
         "https://docs.djangoproject.com/en/5.0/",
-        "http://docs.djangoproject.com/en/5.0/_objects/",
+        "https://docs.djangoproject.com/en/5.0/_objects/",
     ),
+    "mongodb": ("https://www.mongodb.com/docs/languages/python/django-mongodb/v5.0/", None),
     "pymongo": ("https://pymongo.readthedocs.io/en/stable/", None),
     "python": ("https://docs.python.org/3/", None),
 }
 
+root_doc = "contents"
+
 # -- Options for HTML output -------------------------------------------------
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
 
diff --git a/docs/source/contents.rst b/docs/source/contents.rst
new file mode 100644
index 000000000..c044c0e5d
--- /dev/null
+++ b/docs/source/contents.rst
@@ -0,0 +1,23 @@
+=================
+Table of contents
+=================
+
+.. toctree::
+    :hidden:
+
+    index
+
+.. toctree::
+    :maxdepth: 2
+
+    intro/index
+    topics/index
+    ref/index
+    faq
+    internals
+
+Indices
+=======
+
+* :ref:`genindex`
+* :ref:`modindex`
diff --git a/docs/source/faq.rst b/docs/source/faq.rst
new file mode 100644
index 000000000..34e8f247f
--- /dev/null
+++ b/docs/source/faq.rst
@@ -0,0 +1,34 @@
+===
+FAQ
+===
+
+This page contains a list of some frequently asked questions.
+
+Troubleshooting
+===============
+
+Debug logging
+-------------
+
+To troubleshoot MongoDB connectivity issues, you can enable :doc:`PyMongo's
+logging <pymongo:examples/logging>` using :doc:`Django's LOGGING setting
+<django:topics/logging>`.
+
+This is a minimal :setting:`LOGGING` setting that enables PyMongo's ``DEBUG``
+logging::
+
+    LOGGING = {
+        "version": 1,
+        "disable_existing_loggers": False,
+        "handlers": {
+            "console": {
+                "class": "logging.StreamHandler",
+            },
+        },
+        "loggers": {
+            "pymongo": {
+                "handlers": ["console"],
+                "level": "DEBUG",
+            },
+        },
+    }
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 4f80bed6f..1219c031c 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -1,19 +1,48 @@
-django-mongodb-backend 5.0.x documentation
-==========================================
+======================
+Django MongoDB Backend
+======================
 
-.. toctree::
-   :maxdepth: 1
-   :caption: Contents:
+version 5.0.x for Django 5.0.x
 
-   fields
-   querysets
-   forms
-   models
-   embedded-models
+.. rubric:: Everything you need to know about Django MongoDB Backend.
 
-Indices and tables
-==================
+First steps
+===========
 
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
+**Getting started:**
+
+- :doc:`Installation <intro/install>`
+- :doc:`Configuration <intro/configure>`
+
+Getting help
+============
+
+Having trouble? We’d like to help!
+
+- Try the :doc:`faq` – it’s got answers to many common questions.
+
+- Looking for specific information? Try the :ref:`genindex`, :ref:`modindex`,
+  or the detailed :doc:`table of contents <contents>`.
+
+- Didn't find an answer? You're welcome to ask questions or give feedback on
+  the `MongoDB Community Forum <https://www.mongodb.com/community/forums/tag/python>`_.
+
+- Report bugs and request features in our :ref:`issue tracker <issue-tracker>`.
+
+Models
+======
+
+**Reference material:**
+
+- :doc:`ref/models/fields`
+- :doc:`ref/models/querysets`
+- :doc:`ref/models/models`
+
+**Topic guides:**
+
+- :doc:`topics/embedded-models`
+
+Forms
+=====
+
+- :doc:`ref/forms`
diff --git a/docs/source/internals.rst b/docs/source/internals.rst
new file mode 100644
index 000000000..6403bcbec
--- /dev/null
+++ b/docs/source/internals.rst
@@ -0,0 +1,25 @@
+=================
+Project internals
+=================
+
+Documentation for people working on Django MongoDB Backend itself. This is the
+place to go if you'd like to help improve Django MongoDB Backend or learn about
+how the project is managed.
+
+.. _issue-tracker:
+
+Issue tracker
+=============
+
+To report a bug or to request a new feature in Django MongoDB Backend, please
+open an issue in our issue tracker, JIRA:
+
+1. Create a `JIRA account <https://jira.mongodb.org/>`_.
+
+2. Navigate to the `Python Integrations project
+   <https://jira.mongodb.org/projects/INTPYTHON/>`_.
+
+3. Click **Create Issue**. Please provide as much information as possible about
+   the issue and the steps to reproduce it.
+
+Bug reports in JIRA for this project can be viewed by everyone.
diff --git a/docs/source/intro/configure.rst b/docs/source/intro/configure.rst
new file mode 100644
index 000000000..c1886c2e9
--- /dev/null
+++ b/docs/source/intro/configure.rst
@@ -0,0 +1,157 @@
+===================================================
+Configuring a project to use Django MongoDB Backend
+===================================================
+
+Aftering :doc:`installing Django MongoDB Backend <install>`, you must take some
+additional steps to configure your project.
+
+.. _specifying the-default-pk-field:
+
+Specifying the default primary key field
+========================================
+
+In your Django settings, you must specify that all models should use
+:class:`~django_mongodb_backend.fields.ObjectIdAutoField`.
+
+You can create a new project that's configured based on these steps using a
+project template:
+
+.. code-block:: bash
+
+    $ django-admin startproject mysite --template https://github.com/mongodb-labs/django-mongodb-project/archive/refs/heads/5.0.x.zip
+
+(If you're using a version of Django other than 5.0.x, replace the two numbers
+to match the first two numbers from your version.)
+
+This template includes the following line in ``settings.py``::
+
+    DEFAULT_AUTO_FIELD = "django_mongodb_backend.fields.ObjectIdAutoField"
+
+But this setting won't override any apps that have an ``AppConfig`` that
+specifies :attr:`~django.apps.AppConfig.default_auto_field`. For those apps,
+you'll need to create a custom :class:`~django.apps.AppConfig`.
+
+For example, the project template includes ``<project_name>/apps.py``::
+
+    from django.contrib.admin.apps import AdminConfig
+    from django.contrib.auth.apps import AuthConfig
+    from django.contrib.contenttypes.apps import ContentTypesConfig
+
+
+    class MongoAdminConfig(AdminConfig):
+        default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField"
+
+
+    class MongoAuthConfig(AuthConfig):
+        default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField"
+
+
+    class MongoContentTypesConfig(ContentTypesConfig):
+        default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField"
+
+Each app reference in the :setting:`INSTALLED_APPS` setting must point to the
+corresponding ``AppConfig``. For example, instead of ``'django.contrib.admin'``,
+the template uses ``'<project_name>.apps.MongoAdminConfig'``.
+
+Configuring migrations
+======================
+
+Because all models must use
+:class:`~django_mongodb_backend.fields.ObjectIdAutoField`, each third-party
+and contrib app you use needs to have its own migrations specific to MongoDB.
+
+For example, ``settings.py`` in the project template specifies::
+
+    MIGRATION_MODULES = {
+        "admin": "mongo_migrations.admin",
+        "auth": "mongo_migrations.auth",
+        "contenttypes": "mongo_migrations.contenttypes",
+    }
+
+The project template includes these migrations, but you can generate them if
+you're setting things up manually or if you need to create migrations for
+third-party apps. For example:
+
+.. code-block:: bash
+
+    $ python manage.py makemigrations admin auth contenttypes
+    Migrations for 'admin':
+      mongo_migrations/admin/0001_initial.py
+        - Create model LogEntry
+    ...
+
+Creating Django applications
+============================
+
+Whenever you run ``python manage.py startapp``, you must remove the line::
+
+    default_auto_field = 'django.db.models.BigAutoField'
+
+from the new application's ``apps.py`` file (or change it to reference
+``"django_mongodb_backend.fields.ObjectIdAutoField"``).
+
+Alternatively, you can use the following :djadmin:`startapp` template which
+includes this change:
+
+.. code-block:: bash
+
+    $ python manage.py startapp myapp --template https://github.com/mongodb-labs/django-mongodb-app/archive/refs/heads/5.0.x.zip
+
+(If you're using a version of Django other than 5.0.x, replace the two numbers
+to match the first two numbers from your version.)
+
+.. _configuring-databases-setting:
+
+Configuring the ``DATABASES`` setting
+=====================================
+
+After you've set up a project, configure Django's :setting:`DATABASES` setting
+similar to this::
+
+    DATABASES = {
+        "default": {
+            "ENGINE": "django_mongodb_backend",
+            "HOST": "mongodb+srv://cluster0.example.mongodb.net",
+            "NAME": "my_database",
+            "USER": "my_user",
+            "PASSWORD": "my_password",
+            "PORT": 27017,
+            "OPTIONS": {
+                # Example:
+                "retryWrites": "true",
+                "w": "majority",
+                "tls": "false",
+            },
+        },
+    }
+
+For a localhost configuration, you can omit :setting:`HOST` or specify
+``"HOST": "localhost"``.
+
+:setting:`HOST` only needs a scheme prefix for SRV connections
+(``mongodb+srv://``). A ``mongodb://`` prefix is never required.
+
+:setting:`OPTIONS` is an optional dictionary of parameters that will be passed
+to :class:`~pymongo.mongo_client.MongoClient`.
+
+Specify :setting:`USER` and :setting:`PASSWORD` if your database requires
+authentication.
+
+:setting:`PORT` is optional if unchanged from MongoDB's default of 27017.
+
+For a replica set or sharded cluster where you have multiple hosts, include
+all of them in :setting:`HOST`, e.g.
+``"mongodb://mongos0.example.com:27017,mongos1.example.com:27017"``.
+
+Alternatively, if you prefer to simply paste in a MongoDB URI rather than parse
+it into the format above, you can use
+:func:`~django_mongodb_backend.utils.parse_uri`::
+
+    import django_mongodb_backend
+
+    MONGODB_URI = "mongodb+srv://my_user:my_password@cluster0.example.mongodb.net/myDatabase?retryWrites=true&w=majority&tls=false"
+    DATABASES["default"] = django_mongodb_backend.parse_uri(MONGODB_URI)
+
+This constructs a :setting:`DATABASES` setting equivalent to the first example.
+
+Congratulations, your project is ready to go!
diff --git a/docs/source/intro/index.rst b/docs/source/intro/index.rst
new file mode 100644
index 000000000..30bcbbc52
--- /dev/null
+++ b/docs/source/intro/index.rst
@@ -0,0 +1,26 @@
+===============
+Getting started
+===============
+
+New to Django or MongoDB? Well, you came to the right place: read this material
+to quickly get up and running.
+
+.. toctree::
+   :maxdepth: 1
+
+   install
+   configure
+
+.. seealso::
+
+    If you're new to Django_, you might want to start by getting an idea of
+    what it's like.
+
+    For a tutorial that covers using this library with MongoDB Atlas (a fully
+    managed cloud database service that hosts MongoDB), read
+    :doc:`mongodb:get-started`.
+
+    Another excellent tutorial that covers Django in more detail is the
+    :doc:`official Django tutorial <django:intro/tutorial01>`.
+
+    .. _Django: https://www.djangoproject.org/
diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst
new file mode 100644
index 000000000..5348374c0
--- /dev/null
+++ b/docs/source/intro/install.rst
@@ -0,0 +1,17 @@
+=================================
+Installing Django MongoDB Backend
+=================================
+
+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.0.x:
+
+.. code-block:: bash
+
+    $ pip install --pre django-mongodb-backend==5.0.*
+
+(Until the package is out of beta, you must use pip's ``--pre`` option.)
+
+The minor release number of Django doesn't correspond to the minor release
+number of ``django-mongodb-backend``. Use the latest minor release of each.
+
+Next, you'll have to :doc:`configure your project <configure>`.
diff --git a/docs/source/forms.rst b/docs/source/ref/forms.rst
similarity index 100%
rename from docs/source/forms.rst
rename to docs/source/ref/forms.rst
diff --git a/docs/source/ref/index.rst b/docs/source/ref/index.rst
new file mode 100644
index 000000000..08fac9240
--- /dev/null
+++ b/docs/source/ref/index.rst
@@ -0,0 +1,10 @@
+=============
+API reference
+=============
+
+.. toctree::
+   :maxdepth: 2
+
+   models/index
+   forms
+   utils
diff --git a/docs/source/fields.rst b/docs/source/ref/models/fields.rst
similarity index 97%
rename from docs/source/fields.rst
rename to docs/source/ref/models/fields.rst
index d8f46297e..83bd48480 100644
--- a/docs/source/fields.rst
+++ b/docs/source/ref/models/fields.rst
@@ -248,7 +248,7 @@ Stores a model of type ``embedded_model``.
             class Book(models.Model):
                 author = EmbeddedModelField(Author)
 
-See :doc:`embedded-models` for more details and examples.
+See :doc:`/topics/embedded-models` for more details and examples.
 
 .. admonition:: Migrations support is limited
 
@@ -260,6 +260,14 @@ See :doc:`embedded-models` for more details and examples.
     created these models and then added an indexed field to ``Address``,
     the index created in the nested ``Book`` embed is not created.
 
+``ObjectIdAutoField``
+---------------------
+
+.. class:: ObjectIdAutoField
+
+This field is typically the default primary key field for all models stored in
+MongoDB. See :ref:`specifying the-default-pk-field`.
+
 ``ObjectIdField``
 -----------------
 
diff --git a/docs/source/ref/models/index.rst b/docs/source/ref/models/index.rst
new file mode 100644
index 000000000..d5cb63ce1
--- /dev/null
+++ b/docs/source/ref/models/index.rst
@@ -0,0 +1,12 @@
+======
+Models
+======
+
+Model API reference.
+
+.. toctree::
+   :maxdepth: 1
+
+   fields
+   querysets
+   models
diff --git a/docs/source/models.rst b/docs/source/ref/models/models.rst
similarity index 81%
rename from docs/source/models.rst
rename to docs/source/ref/models/models.rst
index d0d200547..b85505bdb 100644
--- a/docs/source/models.rst
+++ b/docs/source/ref/models/models.rst
@@ -7,8 +7,8 @@ One MongoDB-specific model is available in ``django_mongodb_backend.models``.
 
 .. class:: EmbeddedModel
 
-An abstract model which all :doc:`embedded models <embedded-models>` must
-subclass.
+An abstract model which all :doc:`embedded models </topics/embedded-models>`
+must subclass.
 
 Since these models are not stored in their own collection, they do not have
 any of the normal ``QuerySet`` methods (``all()``, ``filter()``, ``delete()``,
diff --git a/docs/source/querysets.rst b/docs/source/ref/models/querysets.rst
similarity index 63%
rename from docs/source/querysets.rst
rename to docs/source/ref/models/querysets.rst
index 4c024c773..e8a7afc5a 100644
--- a/docs/source/querysets.rst
+++ b/docs/source/ref/models/querysets.rst
@@ -1,6 +1,43 @@
+==========================
 ``QuerySet`` API reference
 ==========================
 
+Supported ``QuerySet`` methods
+==============================
+
+All of Django's :doc:`QuerySet methods <django:ref/models/querysets>` are
+supported, except:
+
+  - :meth:`bulk_update() <django.db.models.query.QuerySet.bulk_update>`
+  - :meth:`dates() <django.db.models.query.QuerySet.dates>`
+  - :meth:`datetimes() <django.db.models.query.QuerySet.datetimes>`
+  - :meth:`distinct() <django.db.models.query.QuerySet.distinct>`
+  - :meth:`extra() <django.db.models.query.QuerySet.extra>`
+  - :meth:`prefetch_related() <django.db.models.query.QuerySet.prefetch_related>`
+
+In addition, :meth:`QuerySet.delete() <django.db.models.query.QuerySet.delete>`
+and :meth:`update() <django.db.models.query.QuerySet.update>` do not support
+queries that span multiple collections.
+
+``QuerySet.explain()``
+======================
+
+- :meth:`QuerySet.explain() <django.db.models.query.QuerySet.explain>` supports
+  the `comment and verbosity options
+  <https://www.mongodb.com/docs/manual/reference/command/explain/#command-fields>`_.
+
+  Example::
+
+      Model.objects.explain(comment="...", verbosity="...")
+
+  Valid values for ``verbosity`` are ``"queryPlanner"`` (default),
+  ``"executionStats"``, and ``"allPlansExecution"``.
+
+MongoDB-specific ``QuerySet`` methods
+=====================================
+
+.. class:: django_mongodb_backend.managers.MongoManager
+
 Some MongoDB-specific ``QuerySet`` methods are available by adding a custom
 :class:`~django.db.models.Manager`, ``MongoManager``, to your model::
 
diff --git a/docs/source/ref/utils.rst b/docs/source/ref/utils.rst
new file mode 100644
index 000000000..16a61165c
--- /dev/null
+++ b/docs/source/ref/utils.rst
@@ -0,0 +1,38 @@
+===================
+Utils API reference
+===================
+
+.. module:: django_mongodb_backend.utils
+   :synopsis: Built-in utilities.
+
+This document covers the public API parts of ``django_mongodb_backend.utils``.
+Most of the module's contents are designed for internal use and only the
+following parts can be considered stable.
+
+``parse_uri()``
+===============
+
+.. function:: parse_uri(uri, conn_max_age=0, test=None)
+
+Parses a MongoDB `connection string`_ into a dictionary suitable for Django's
+:setting:`DATABASES` setting.
+
+.. _connection string: https://www.mongodb.com/docs/manual/reference/connection-string/
+
+Example::
+
+    import django_mongodb_backend
+
+    MONGODB_URI = "mongodb+srv://my_user:my_password@cluster0.example.mongodb.net/myDatabase?retryWrites=true&w=majority&tls=false"
+    DATABASES["default"] = django_mongodb_backend.parse_uri(MONGODB_URI)
+
+You can use the parameters to customize the resulting :setting:`DATABASES`
+setting:
+
+- Use ``conn_max_age`` to configure :ref:`persistent database connections
+  <persistent-database-connections>`.
+- Use ``test`` to provide a dictionary of settings for test databases in the
+  format of :setting:`TEST <DATABASE-TEST>`.
+
+But for maximum flexibility, construct :setting:`DATABASES` manually as
+described in :ref:`configuring-databases-setting`.
diff --git a/docs/source/embedded-models.rst b/docs/source/topics/embedded-models.rst
similarity index 100%
rename from docs/source/embedded-models.rst
rename to docs/source/topics/embedded-models.rst
diff --git a/docs/source/topics/index.rst b/docs/source/topics/index.rst
new file mode 100644
index 000000000..63ff9a250
--- /dev/null
+++ b/docs/source/topics/index.rst
@@ -0,0 +1,12 @@
+============================
+Using Django MongoDB Backend
+============================
+
+Introductions to some of the key parts of Django MongoDB Backend you'll need to
+know:
+
+.. toctree::
+   :maxdepth: 2
+
+   embedded-models
+   known-issues
diff --git a/docs/source/topics/known-issues.rst b/docs/source/topics/known-issues.rst
new file mode 100644
index 000000000..005727868
--- /dev/null
+++ b/docs/source/topics/known-issues.rst
@@ -0,0 +1,102 @@
+============================
+Known issues and limitations
+============================
+
+This document summarizes some known issues and limitations of this library.
+If you notice an issue not listed, use the :ref:`issue-tracker` to report a bug
+or request a feature.
+
+Like any database, MongoDB has some particularities. Also keep in mind that
+because MongoDB is a NoSQL database, it's impossible to implement SQL-specific
+functionality.
+
+Model fields
+============
+
+- :class:`~django.db.models.DateTimeField` is limited to millisecond precision
+  (rather than microsecond like most other databases), and correspondingly,
+  :class:`~django.db.models.DurationField` stores milliseconds rather than
+  microseconds.
+
+- Some of Django's built-in fields aren't supported by MongoDB:
+
+  - :class:`~django.db.models.AutoField` (including
+    :class:`~django.db.models.BigAutoField` and
+    :class:`~django.db.models.SmallAutoField`)
+  - :class:`~django.db.models.GeneratedField`
+
+Querying
+========
+
+- The following ``QuerySet`` methods aren't supported:
+
+  - :meth:`bulk_update() <django.db.models.query.QuerySet.bulk_update>`
+  - :meth:`dates() <django.db.models.query.QuerySet.dates>`
+  - :meth:`datetimes() <django.db.models.query.QuerySet.datetimes>`
+  - :meth:`distinct() <django.db.models.query.QuerySet.distinct>`
+  - :meth:`extra() <django.db.models.query.QuerySet.extra>`
+  - :meth:`prefetch_related() <django.db.models.query.QuerySet.prefetch_related>`
+
+- :meth:`QuerySet.delete() <django.db.models.query.QuerySet.delete>` and
+  :meth:`update() <django.db.models.query.QuerySet.update>` do not support queries
+  that span multiple collections.
+
+- When querying :class:`~django.db.models.JSONField`:
+
+  - There is no way to distinguish between a JSON ``"null"`` (represented by
+    ``Value(None, JSONField())``) and a SQL ``null`` (queried using the
+    :lookup:`isnull` lookup). Both of these queries return both of these nulls.
+  - Some queries with ``Q`` objects, e.g. ``Q(value__foo="bar")``, don't work
+    properly, particularly with ``QuerySet.exclude()``.
+  - Filtering for a ``None`` key, e.g. ``QuerySet.filter(value__j=None)``
+    incorrectly returns objects where the key doesn't exist.
+  - You can study the skipped tests in ``DatabaseFeatures.django_test_skips``
+    for more details on known issues.
+
+Database functions
+==================
+
+- Some of Django's built-in database functions aren't supported by MongoDB:
+
+  - :class:`~django.db.models.functions.Chr`
+  - :class:`~django.db.models.functions.ExtractQuarter`
+  - :class:`~django.db.models.functions.LPad`,
+    :class:`~django.db.models.functions.RPad`
+  - :class:`~django.db.models.functions.MD5`
+  - :class:`~django.db.models.functions.Now`
+  - :class:`~django.db.models.functions.Ord`
+  - :class:`~django.db.models.functions.Repeat`
+  - :class:`~django.db.models.functions.Reverse`
+  - :class:`~django.db.models.functions.Right`
+  - :class:`~django.db.models.functions.SHA1`,
+    :class:`~django.db.models.functions.SHA224`,
+    :class:`~django.db.models.functions.SHA256`,
+    :class:`~django.db.models.functions.SHA384`,
+    :class:`~django.db.models.functions.SHA512`
+  - :class:`~django.db.models.functions.Sign`
+
+- The ``tzinfo`` parameter of the :class:`~django.db.models.functions.Trunc`
+  database functions doesn't work properly because MongoDB converts the result
+  back to UTC.
+
+Transaction management
+======================
+
+Query execution uses Django and MongoDB's default behavior of autocommit mode.
+Each query is immediately committed to the database.
+
+Django's :doc:`transaction management APIs <django:topics/db/transactions>`
+are not supported.
+
+Database introspection
+======================
+
+Due to the lack of ability to introspect MongoDB collection schema,
+:djadmin:`inspectdb` and :option:`migrate --fake-initial` aren't supported.
+
+Caching
+=======
+
+:ref:`Database caching <database-caching>` is not supported since the built-in
+database cache backend requires SQL. A custom cache backend for MongoDB will be
+provided in the future.
diff --git a/pyproject.toml b/pyproject.toml
index 676504748..fbd8d9023 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -119,3 +119,6 @@ partial_branches = ["if (.*and +)*not _use_c( and.*)*:"]
 
 [tool.coverage.html]
 directory = "htmlcov"
+
+[tool.rstcheck]
+report_level = "WARNING"

From 8f81d58987bba4931089f2fbe7989c72babaa5e1 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Wed, 19 Feb 2025 11:05:29 -0500
Subject: [PATCH 16/25] Update links for repo move from mongodb-labs to mongodb

---
 django_mongodb_backend/features.py | 4 ++--
 pyproject.toml                     | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/django_mongodb_backend/features.py b/django_mongodb_backend/features.py
index 303393b20..e2547c207 100644
--- a/django_mongodb_backend/features.py
+++ b/django_mongodb_backend/features.py
@@ -34,7 +34,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     supports_temporal_subtraction = True
     # MongoDB stores datetimes in UTC.
     supports_timezones = False
-    # Not implemented: https://github.com/mongodb-labs/django-mongodb-backend/issues/7
+    # Not implemented: https://github.com/mongodb/django-mongodb-backend/issues/7
     supports_transactions = False
     supports_unspecified_pk = True
     uses_savepoints = False
@@ -562,7 +562,7 @@ def django_test_expected_failures(self):
             "cache.tests.DBCacheWithTimeZoneTests",
         },
         "FilteredRelation not supported.": {
-            # https://github.com/mongodb-labs/django-mongodb-backend/issues/157
+            # https://github.com/mongodb/django-mongodb-backend/issues/157
             "filtered_relation.tests.FilteredRelationAggregationTests",
             "filtered_relation.tests.FilteredRelationAnalyticalAggregationTests",
             "filtered_relation.tests.FilteredRelationTests",
diff --git a/pyproject.toml b/pyproject.toml
index fbd8d9023..65a377d71 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -38,8 +38,8 @@ docs = [ "sphinx>=7"]
 [project.urls]
 Homepage = "https://www.mongodb.org"
 Documentation = "https://django-mongodb-backend.readthedocs.io"
-Source = "https://github.com/mongodb-labs/django-mongodb-backend"
-Tracker = "https://github.com/mongodb-labs/django-mongodb-backend/issues"
+Source = "https://github.com/mongodb/django-mongodb-backend"
+Tracker = "https://github.com/mongodb/django-mongodb-backend/issues"
 
 [tool.hatch.version]
 path = "django_mongodb_backend/__init__.py"

From 19cf49432ea3964c354b0e29022b0c5b08c14427 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Tue, 4 Feb 2025 09:17:09 -0500
Subject: [PATCH 17/25] INTPYTHON-509 Add db_name parameter to parse_uri()

---
 .github/workflows/mongodb_settings.py  |  2 +-
 README.md                              | 17 +++------
 django_mongodb_backend/utils.py        |  9 +++--
 docs/source/ref/utils.rst              |  9 +++--
 tests/backend_/utils/test_parse_uri.py | 49 ++++++++++++++++++++------
 5 files changed, 58 insertions(+), 28 deletions(-)

diff --git a/.github/workflows/mongodb_settings.py b/.github/workflows/mongodb_settings.py
index f33d458ac..bdcc696cb 100644
--- a/.github/workflows/mongodb_settings.py
+++ b/.github/workflows/mongodb_settings.py
@@ -3,7 +3,7 @@
 from django_mongodb_backend import parse_uri
 
 if mongodb_uri := os.getenv("MONGODB_URI"):
-    db_settings = parse_uri(mongodb_uri)
+    db_settings = parse_uri(mongodb_uri, db_name="dummy")
 
     # Workaround for https://github.com/mongodb-labs/mongo-orchestration/issues/268
     if db_settings["USER"] and db_settings["PASSWORD"]:
diff --git a/README.md b/README.md
index 63691e61d..20a5ef543 100644
--- a/README.md
+++ b/README.md
@@ -46,24 +46,17 @@ $ django-admin startproject example --template https://github.com/mongodb-labs/d
 
 ### Connect to the database
 
-Navigate to your `example/settings.py` file and find the variable named
-`DATABASES` Replace the `DATABASES` variable with this:
+Navigate to your `example/settings.py` file and replace the `DATABASES`
+setting like so:
 
 ```python
 DATABASES = {
-    "default": django_mongodb_backend.parse_uri("<CONNECTION_STRING_URI>"),
+    "default": django_mongodb_backend.parse_uri(
+        "<CONNECTION_STRING_URI>", db_name="example"
+    ),
 }
 ```
 
-The MongoDB `<CONNECTION_STRING_URI>` must also specify a database for the
-`parse_uri` function to work.
-If not already included, make sure you provide a value for `<DATABASE_NAME>`
-in your URI as shown in the example below:
-```bash
-mongodb+srv://myDatabaseUser:D1fficultP%40ssw0rd@cluster0.example.mongodb.net/<DATABASE_NAME>?retryWrites=true&w=majority
-```
-
-
 ### Run the server
 To verify that you installed Django MongoDB Backend and correctly configured your project, run the following command from your project root:
 ```bash
diff --git a/django_mongodb_backend/utils.py b/django_mongodb_backend/utils.py
index c389d93bc..95e3b5a8e 100644
--- a/django_mongodb_backend/utils.py
+++ b/django_mongodb_backend/utils.py
@@ -28,7 +28,7 @@ def check_django_compatability():
         )
 
 
-def parse_uri(uri, conn_max_age=0, test=None):
+def parse_uri(uri, *, db_name=None, conn_max_age=0, test=None):
     """
     Convert the given uri into a dictionary suitable for Django's DATABASES
     setting.
@@ -45,9 +45,12 @@ def parse_uri(uri, conn_max_age=0, test=None):
             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.")
     settings_dict = {
         "ENGINE": "django_mongodb_backend",
-        "NAME": uri["database"],
+        "NAME": db_name,
         "HOST": host,
         "PORT": port,
         "USER": uri.get("username"),
@@ -55,6 +58,8 @@ def parse_uri(uri, conn_max_age=0, test=None):
         "OPTIONS": uri.get("options"),
         "CONN_MAX_AGE": conn_max_age,
     }
+    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
diff --git a/docs/source/ref/utils.rst b/docs/source/ref/utils.rst
index 16a61165c..a5fb8ff3e 100644
--- a/docs/source/ref/utils.rst
+++ b/docs/source/ref/utils.rst
@@ -12,7 +12,7 @@ following parts can be considered stable.
 ``parse_uri()``
 ===============
 
-.. function:: parse_uri(uri, conn_max_age=0, test=None)
+.. function:: parse_uri(uri, db_name=None, conn_max_age=0, test=None)
 
 Parses a MongoDB `connection string`_ into a dictionary suitable for Django's
 :setting:`DATABASES` setting.
@@ -23,8 +23,11 @@ Example::
 
     import django_mongodb_backend
 
-    MONGODB_URI = "mongodb+srv://my_user:my_password@cluster0.example.mongodb.net/myDatabase?retryWrites=true&w=majority&tls=false"
-    DATABASES["default"] = django_mongodb_backend.parse_uri(MONGODB_URI)
+    MONGODB_URI = "mongodb+srv://my_user:my_password@cluster0.example.mongodb.net/defaultauthdb?retryWrites=true&w=majority&tls=false"
+    DATABASES["default"] = django_mongodb_backend.parse_uri(MONGODB_URI, db_name="example")
+
+You must specify ``db_name`` (the :setting:`NAME` of your database) if the URI
+doesn't specify ``defaultauthdb``.
 
 You can use the parameters to customize the resulting :setting:`DATABASES`
 setting:
diff --git a/tests/backend_/utils/test_parse_uri.py b/tests/backend_/utils/test_parse_uri.py
index c4d475f1f..a28983596 100644
--- a/tests/backend_/utils/test_parse_uri.py
+++ b/tests/backend_/utils/test_parse_uri.py
@@ -1,6 +1,7 @@
 from unittest.mock import patch
 
 import pymongo
+from django.core.exceptions import ImproperlyConfigured
 from django.test import SimpleTestCase
 
 from django_mongodb_backend import parse_uri
@@ -12,11 +13,28 @@ def test_simple_uri(self):
         self.assertEqual(settings_dict["ENGINE"], "django_mongodb_backend")
         self.assertEqual(settings_dict["NAME"], "myDatabase")
         self.assertEqual(settings_dict["HOST"], "cluster0.example.mongodb.net")
+        self.assertEqual(settings_dict["OPTIONS"], {"authSource": "myDatabase"})
 
-    def test_no_database(self):
-        settings_dict = parse_uri("mongodb://cluster0.example.mongodb.net")
-        self.assertIsNone(settings_dict["NAME"])
+    def test_db_name(self):
+        settings_dict = parse_uri("mongodb://cluster0.example.mongodb.net/", db_name="myDatabase")
+        self.assertEqual(settings_dict["ENGINE"], "django_mongodb_backend")
+        self.assertEqual(settings_dict["NAME"], "myDatabase")
+        self.assertEqual(settings_dict["HOST"], "cluster0.example.mongodb.net")
+        self.assertEqual(settings_dict["OPTIONS"], {})
+
+    def test_db_name_overrides_default_auth_db(self):
+        settings_dict = parse_uri(
+            "mongodb://cluster0.example.mongodb.net/default_auth_db", db_name="myDatabase"
+        )
+        self.assertEqual(settings_dict["ENGINE"], "django_mongodb_backend")
+        self.assertEqual(settings_dict["NAME"], "myDatabase")
         self.assertEqual(settings_dict["HOST"], "cluster0.example.mongodb.net")
+        self.assertEqual(settings_dict["OPTIONS"], {"authSource": "default_auth_db"})
+
+    def test_no_database(self):
+        msg = "You must provide the db_name parameter."
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
+            parse_uri("mongodb://cluster0.example.mongodb.net")
 
     def test_srv_uri_with_options(self):
         uri = "mongodb+srv://my_user:my_password@cluster0.example.mongodb.net/my_database?retryWrites=true&w=majority"
@@ -30,35 +48,46 @@ def test_srv_uri_with_options(self):
         self.assertEqual(settings_dict["PASSWORD"], "my_password")
         self.assertIsNone(settings_dict["PORT"])
         self.assertEqual(
-            settings_dict["OPTIONS"], {"retryWrites": True, "w": "majority", "tls": True}
+            settings_dict["OPTIONS"],
+            {"authSource": "my_database", "retryWrites": True, "w": "majority", "tls": True},
         )
 
     def test_localhost(self):
-        settings_dict = parse_uri("mongodb://localhost")
+        settings_dict = parse_uri("mongodb://localhost/db")
         self.assertEqual(settings_dict["HOST"], "localhost")
         self.assertEqual(settings_dict["PORT"], 27017)
 
     def test_localhost_with_port(self):
-        settings_dict = parse_uri("mongodb://localhost:27018")
+        settings_dict = parse_uri("mongodb://localhost:27018/db")
         self.assertEqual(settings_dict["HOST"], "localhost")
         self.assertEqual(settings_dict["PORT"], 27018)
 
     def test_hosts_with_ports(self):
-        settings_dict = parse_uri("mongodb://localhost:27017,localhost:27018")
+        settings_dict = parse_uri("mongodb://localhost:27017,localhost:27018/db")
         self.assertEqual(settings_dict["HOST"], "localhost:27017,localhost:27018")
         self.assertEqual(settings_dict["PORT"], None)
 
     def test_hosts_without_ports(self):
-        settings_dict = parse_uri("mongodb://host1.net,host2.net")
+        settings_dict = parse_uri("mongodb://host1.net,host2.net/db")
         self.assertEqual(settings_dict["HOST"], "host1.net:27017,host2.net:27017")
         self.assertEqual(settings_dict["PORT"], None)
 
+    def test_auth_source_in_query_string(self):
+        settings_dict = parse_uri("mongodb://localhost/?authSource=auth", db_name="db")
+        self.assertEqual(settings_dict["NAME"], "db")
+        self.assertEqual(settings_dict["OPTIONS"], {"authSource": "auth"})
+
+    def test_auth_source_in_query_string_overrides_defaultauthdb(self):
+        settings_dict = parse_uri("mongodb://localhost/db?authSource=auth")
+        self.assertEqual(settings_dict["NAME"], "db")
+        self.assertEqual(settings_dict["OPTIONS"], {"authSource": "auth"})
+
     def test_conn_max_age(self):
-        settings_dict = parse_uri("mongodb://localhost", conn_max_age=600)
+        settings_dict = parse_uri("mongodb://localhost/db", conn_max_age=600)
         self.assertEqual(settings_dict["CONN_MAX_AGE"], 600)
 
     def test_test_kwarg(self):
-        settings_dict = parse_uri("mongodb://localhost", test={"NAME": "test_db"})
+        settings_dict = parse_uri("mongodb://localhost/db", test={"NAME": "test_db"})
         self.assertEqual(settings_dict["TEST"], {"NAME": "test_db"})
 
     def test_invalid_credentials(self):

From b7f8154a50580e3247aeb4223ea1c8474f6a46f1 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Wed, 19 Feb 2025 19:56:00 -0500
Subject: [PATCH 18/25] Add release notes

---
 docs/source/contents.rst       |  1 +
 docs/source/index.rst          |  6 ++++++
 docs/source/releases/5.0.x.rst | 22 ++++++++++++++++++++++
 docs/source/releases/index.rst | 14 ++++++++++++++
 4 files changed, 43 insertions(+)
 create mode 100644 docs/source/releases/5.0.x.rst
 create mode 100644 docs/source/releases/index.rst

diff --git a/docs/source/contents.rst b/docs/source/contents.rst
index c044c0e5d..ab6b5073d 100644
--- a/docs/source/contents.rst
+++ b/docs/source/contents.rst
@@ -14,6 +14,7 @@ Table of contents
     topics/index
     ref/index
     faq
+    releases/index
     internals
 
 Indices
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 1219c031c..57c553644 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -46,3 +46,9 @@ Forms
 =====
 
 - :doc:`ref/forms`
+
+Miscellaneous
+=============
+
+- :doc:`releases/index`
+- :doc:`internals`
diff --git a/docs/source/releases/5.0.x.rst b/docs/source/releases/5.0.x.rst
new file mode 100644
index 000000000..26445e905
--- /dev/null
+++ b/docs/source/releases/5.0.x.rst
@@ -0,0 +1,22 @@
+============================
+Django MongoDB Backend 5.0.x
+============================
+
+5.0.0 beta 1
+============
+
+*Unreleased*
+
+- Fixed the inability to save nested embedded model forms.
+- Fixed :ref:`persistent database connections
+  <django:persistent-database-connections>`.
+- Added :doc:`async <django:topics/async>` support.
+- Added the ``db_name`` parameter to
+  :func:`~django_mongodb_backend.utils.parse_uri`.
+
+5.0.0 beta 0
+============
+
+*January 29, 2025*
+
+Initial Public Preview release.
diff --git a/docs/source/releases/index.rst b/docs/source/releases/index.rst
new file mode 100644
index 000000000..391709840
--- /dev/null
+++ b/docs/source/releases/index.rst
@@ -0,0 +1,14 @@
+=============
+Release notes
+=============
+
+The release notes will tell you what's new in each version and will also
+describe any backwards-incompatible changes.
+
+Below are release notes through Django MongoDB backend 5.0.x. Newer versions of
+the documentation contain the release notes for any later releases.
+
+.. toctree::
+   :maxdepth: 1
+
+   5.0.x

From 8e7e8db5ee15dcbe1266edf65f7a65c8a977c21f Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Mon, 10 Feb 2025 18:55:24 -0500
Subject: [PATCH 19/25] INTPYTHON-393 Remove ObjectIdAutoField acceptance of
 integer values

---
 django_mongodb_backend/features.py    |  5 ++++
 django_mongodb_backend/fields/auto.py | 34 +++------------------------
 docs/source/contents.rst              |  1 +
 docs/source/howto/contrib-apps.rst    | 26 ++++++++++++++++++++
 docs/source/howto/index.rst           | 13 ++++++++++
 docs/source/index.rst                 |  3 ++-
 docs/source/intro/configure.rst       |  4 ++++
 docs/source/releases/5.0.x.rst        |  4 ++++
 tests/indexes_/test_condition.py      |  4 ++--
 tests/model_fields_/test_autofield.py |  7 ++++--
 10 files changed, 65 insertions(+), 36 deletions(-)
 create mode 100644 docs/source/howto/contrib-apps.rst
 create mode 100644 docs/source/howto/index.rst

diff --git a/django_mongodb_backend/features.py b/django_mongodb_backend/features.py
index e2547c207..baa690ce1 100644
--- a/django_mongodb_backend/features.py
+++ b/django_mongodb_backend/features.py
@@ -197,6 +197,8 @@ def django_test_expected_failures(self):
             "lookup.tests.LookupTests.test_in_ignore_none_with_unhashable_items",
             "m2m_through_regress.tests.ThroughLoadDataTestCase.test_sequence_creation",
             "many_to_many.tests.ManyToManyTests.test_add_remove_invalid_type",
+            "many_to_one.tests.ManyToOneTests.test_fk_to_smallautofield",
+            "many_to_one.tests.ManyToOneTests.test_fk_to_bigautofield",
             "migrations.test_operations.OperationTests.test_autofield__bigautofield_foreignfield_growth",
             "migrations.test_operations.OperationTests.test_model_with_bigautofield",
             "migrations.test_operations.OperationTests.test_smallfield_autofield_foreignfield_growth",
@@ -205,6 +207,8 @@ def django_test_expected_failures(self):
             "model_fields.test_autofield.BigAutoFieldTests",
             "model_fields.test_autofield.SmallAutoFieldTests",
             "queries.tests.TestInvalidValuesRelation.test_invalid_values",
+            "schema.tests.SchemaTests.test_alter_autofield_pk_to_bigautofield_pk",
+            "schema.tests.SchemaTests.test_alter_autofield_pk_to_smallautofield_pk",
         },
         "Converters aren't run on returning fields from insert.": {
             # Unsure this is needed for this backend. Can implement by request.
@@ -223,6 +227,7 @@ def django_test_expected_failures(self):
             "queries.test_qs_combinators.QuerySetSetOperationTests.test_order_raises_on_non_selected_column",
             "queries.tests.RelatedLookupTypeTests.test_values_queryset_lookup",
             "queries.tests.ValuesSubqueryTests.test_values_in_subquery",
+            "sites_tests.tests.CreateDefaultSiteTests.test_no_site_id",
         },
         "Cannot use QuerySet.delete() when querying across multiple collections on MongoDB.": {
             "admin_changelist.tests.ChangeListTests.test_distinct_for_many_to_many_at_second_level_in_search_fields",
diff --git a/django_mongodb_backend/fields/auto.py b/django_mongodb_backend/fields/auto.py
index 5bf84e67d..6a6f12af5 100644
--- a/django_mongodb_backend/fields/auto.py
+++ b/django_mongodb_backend/fields/auto.py
@@ -1,5 +1,3 @@
-from bson import ObjectId, errors
-from django.core import exceptions
 from django.db.models.fields import AutoField
 from django.utils.functional import cached_property
 
@@ -22,39 +20,13 @@ def deconstruct(self):
         return name, path, args, kwargs
 
     def get_prep_value(self, value):
-        if value is None:
-            return None
-        # Accept int for compatibility with Django's test suite which has many
-        # instances of manually assigned integer IDs, as well as for things
-        # like settings.SITE_ID which has a system check requiring an integer.
-        if isinstance(value, (ObjectId | int)):
-            return value
-        try:
-            return ObjectId(value)
-        except errors.InvalidId as e:
-            # A manually assigned integer ID?
-            if isinstance(value, str) and value.isdigit():
-                return int(value)
-            raise ValueError(f"Field '{self.name}' expected an ObjectId but got {value!r}.") from e
+        # Override to omit super() which would call AutoField/IntegerField's
+        # implementation that requires value to be an integer.
+        return self.to_python(value)
 
     def get_internal_type(self):
         return "ObjectIdAutoField"
 
-    def to_python(self, value):
-        if value is None or isinstance(value, int):
-            return value
-        try:
-            return ObjectId(value)
-        except errors.InvalidId:
-            try:
-                return int(value)
-            except ValueError:
-                raise exceptions.ValidationError(
-                    self.error_messages["invalid"],
-                    code="invalid",
-                    params={"value": value},
-                ) from None
-
     @cached_property
     def validators(self):
         # Avoid IntegerField validators inherited from AutoField.
diff --git a/docs/source/contents.rst b/docs/source/contents.rst
index ab6b5073d..6a102569f 100644
--- a/docs/source/contents.rst
+++ b/docs/source/contents.rst
@@ -13,6 +13,7 @@ Table of contents
     intro/index
     topics/index
     ref/index
+    howto/index
     faq
     releases/index
     internals
diff --git a/docs/source/howto/contrib-apps.rst b/docs/source/howto/contrib-apps.rst
new file mode 100644
index 000000000..7f4a0a01b
--- /dev/null
+++ b/docs/source/howto/contrib-apps.rst
@@ -0,0 +1,26 @@
+=================================
+Configuring Django's contrib apps
+=================================
+
+Generally, Django's contribs app work out of the box, but here are some
+required adjustments.
+
+``contrib.sites``
+=================
+
+Usually the :doc:`sites framework <django:ref/contrib/sites>` requires the
+:setting:`SITE_ID` setting to be an integer corresponding to the primary key of
+the :class:`~django.contrib.sites.models.Site` object. For MongoDB, however,
+all primary keys are :class:`~bson.objectid.ObjectId`\s, and so
+:setting:`SITE_ID` must be set accordingly::
+
+    from bson import ObjectId
+
+    SITE_ID = ObjectId("000000000000000000000001")
+
+You must also use the :setting:`SILENCED_SYSTEM_CHECKS` setting to suppress
+Django's system check requiring :setting:`SITE_ID` to be an integer::
+
+    SILENCED_SYSTEM_CHECKS = [
+        "sites.E101",  # SITE_ID must be an ObjectId for MongoDB.
+    ]
diff --git a/docs/source/howto/index.rst b/docs/source/howto/index.rst
new file mode 100644
index 000000000..95d7ef632
--- /dev/null
+++ b/docs/source/howto/index.rst
@@ -0,0 +1,13 @@
+=============
+How-to guides
+=============
+
+Practical guides covering common tasks and problems.
+
+Project configuration
+=====================
+
+.. toctree::
+   :maxdepth: 1
+
+   contrib-apps
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 57c553644..b39f574bd 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -12,7 +12,8 @@ First steps
 **Getting started:**
 
 - :doc:`Installation <intro/install>`
-- :doc:`Configuration <intro/configure>`
+- :doc:`Configuring a project <intro/configure>`
+- :doc:`howto/contrib-apps`
 
 Getting help
 ============
diff --git a/docs/source/intro/configure.rst b/docs/source/intro/configure.rst
index c1886c2e9..e3a4c945c 100644
--- a/docs/source/intro/configure.rst
+++ b/docs/source/intro/configure.rst
@@ -155,3 +155,7 @@ it into the format above, you can use
 This constructs a :setting:`DATABASES` setting equivalent to the first example.
 
 Congratulations, your project is ready to go!
+
+.. seealso::
+
+    :doc:`/howto/contrib-apps`
diff --git a/docs/source/releases/5.0.x.rst b/docs/source/releases/5.0.x.rst
index 26445e905..bfaa0e632 100644
--- a/docs/source/releases/5.0.x.rst
+++ b/docs/source/releases/5.0.x.rst
@@ -7,6 +7,10 @@ Django MongoDB Backend 5.0.x
 
 *Unreleased*
 
+- Backward-incompatible:
+  :class:`~django_mongodb_backend.fields.ObjectIdAutoField` no longer accepts
+  integer values. The undocumented behavior eased testing with Django's test
+  suite which hardcodes many integer primary key values.
 - Fixed the inability to save nested embedded model forms.
 - Fixed :ref:`persistent database connections
   <django:persistent-database-connections>`.
diff --git a/tests/indexes_/test_condition.py b/tests/indexes_/test_condition.py
index b5b02e9b3..5077dd0bd 100644
--- a/tests/indexes_/test_condition.py
+++ b/tests/indexes_/test_condition.py
@@ -34,7 +34,7 @@ def test_negated_not_supported(self):
             Index(
                 name="test",
                 fields=["headline"],
-                condition=~Q(pk=True),
+                condition=~Q(pk__isnull=True),
             )._get_condition_mql(Article, schema_editor=editor)
 
     def test_xor_not_supported(self):
@@ -43,7 +43,7 @@ def test_xor_not_supported(self):
             Index(
                 name="test",
                 fields=["headline"],
-                condition=Q(pk=True) ^ Q(pk=False),
+                condition=Q(pk__isnull=True) ^ Q(pk__isnull=False),
             )._get_condition_mql(Article, schema_editor=editor)
 
     def test_operations(self):
diff --git a/tests/model_fields_/test_autofield.py b/tests/model_fields_/test_autofield.py
index 1ba2edc41..82b606d5c 100644
--- a/tests/model_fields_/test_autofield.py
+++ b/tests/model_fields_/test_autofield.py
@@ -1,3 +1,4 @@
+from django.core.exceptions import ValidationError
 from django.test import SimpleTestCase
 
 from django_mongodb_backend.fields import ObjectIdAutoField
@@ -15,6 +16,8 @@ def test_get_internal_type(self):
         f = ObjectIdAutoField()
         self.assertEqual(f.get_internal_type(), "ObjectIdAutoField")
 
-    def test_to_python(self):
+    def test_to_python_invalid_value(self):
         f = ObjectIdAutoField()
-        self.assertEqual(f.to_python("1"), 1)
+        msg = "“1” is not a valid Object Id."
+        with self.assertRaisesMessage(ValidationError, msg):
+            f.to_python("1")

From be4e4ad35c6caa5f988943cbafc0ef21acda2e55 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Fri, 21 Feb 2025 20:22:26 -0500
Subject: [PATCH 20/25] Add a database router so dumpdata can ignore embedded
 models

---
 django_mongodb_backend/routers.py | 18 ++++++++++++++++++
 docs/source/faq.rst               |  7 +++++++
 docs/source/intro/configure.rst   | 15 +++++++++++++++
 docs/source/releases/5.0.x.rst    |  3 +++
 tests/models_/models.py           |  6 ++++++
 tests/models_/test_routers.py     | 20 ++++++++++++++++++++
 6 files changed, 69 insertions(+)
 create mode 100644 django_mongodb_backend/routers.py
 create mode 100644 tests/models_/test_routers.py

diff --git a/django_mongodb_backend/routers.py b/django_mongodb_backend/routers.py
new file mode 100644
index 000000000..60e54bbd8
--- /dev/null
+++ b/django_mongodb_backend/routers.py
@@ -0,0 +1,18 @@
+from django.apps import apps
+
+from django_mongodb_backend.models import EmbeddedModel
+
+
+class MongoRouter:
+    def allow_migrate(self, db, app_label, model_name=None, **hints):
+        """
+        EmbeddedModels don't have their own collection and must be ignored by
+        dumpdata.
+        """
+        if not model_name:
+            return None
+        try:
+            model = apps.get_model(app_label, model_name)
+        except LookupError:
+            return None
+        return False if issubclass(model, EmbeddedModel) else None
diff --git a/docs/source/faq.rst b/docs/source/faq.rst
index 34e8f247f..1ea0c2b48 100644
--- a/docs/source/faq.rst
+++ b/docs/source/faq.rst
@@ -32,3 +32,10 @@ logging::
             },
         },
     }
+
+``dumpdata`` fails with ``CommandError: Unable to serialize database``
+----------------------------------------------------------------------
+
+If running ``manage.py dumpdata`` results in ``CommandError: Unable to
+serialize database: 'EmbeddedModelManager' object has no attribute using'``,
+see :ref:`configuring-database-routers-setting`.
diff --git a/docs/source/intro/configure.rst b/docs/source/intro/configure.rst
index e3a4c945c..261ef89a5 100644
--- a/docs/source/intro/configure.rst
+++ b/docs/source/intro/configure.rst
@@ -154,6 +154,21 @@ it into the format above, you can use
 
 This constructs a :setting:`DATABASES` setting equivalent to the first example.
 
+.. _configuring-database-routers-setting:
+
+Configuring the ``DATABASE_ROUTERS`` setting
+============================================
+
+If you intend to use :doc:`embedded models </topics/embedded-models>`, you must
+configure the :setting:`DATABASE_ROUTERS` setting so that a collection for
+these models isn't created and so that embedded models won't be treated as
+normal models by :djadmin:`dumpdata`::
+
+    DATABASE_ROUTERS = ["django_mongodb_backend.routers.MongoRouter"]
+
+(If you've used the :djadmin:`startproject` template, this line is already
+present.)
+
 Congratulations, your project is ready to go!
 
 .. seealso::
diff --git a/docs/source/releases/5.0.x.rst b/docs/source/releases/5.0.x.rst
index bfaa0e632..011647e73 100644
--- a/docs/source/releases/5.0.x.rst
+++ b/docs/source/releases/5.0.x.rst
@@ -17,6 +17,9 @@ Django MongoDB Backend 5.0.x
 - Added :doc:`async <django:topics/async>` support.
 - Added the ``db_name`` parameter to
   :func:`~django_mongodb_backend.utils.parse_uri`.
+- Added ``django_mongodb_backend.routers.MongoRouter`` to allow
+  :djadmin:`dumpdata` to ignore embedded models. See
+  :ref:`configuring-database-routers-setting`.
 
 5.0.0 beta 0
 ============
diff --git a/tests/models_/models.py b/tests/models_/models.py
index e02edda0a..fa4e86ec7 100644
--- a/tests/models_/models.py
+++ b/tests/models_/models.py
@@ -1,5 +1,11 @@
+from django.db import models
+
 from django_mongodb_backend.models import EmbeddedModel
 
 
 class Embed(EmbeddedModel):
     pass
+
+
+class PlainModel(models.Model):
+    pass
diff --git a/tests/models_/test_routers.py b/tests/models_/test_routers.py
new file mode 100644
index 000000000..cec78240c
--- /dev/null
+++ b/tests/models_/test_routers.py
@@ -0,0 +1,20 @@
+from django.test import SimpleTestCase
+
+from django_mongodb_backend.routers import MongoRouter
+
+
+class TestRouter(SimpleTestCase):
+    def setUp(self):
+        self.router = MongoRouter()
+
+    def test_no_model(self):
+        self.assertIsNone(self.router.allow_migrate("db", "models_"))
+
+    def test_regular_model(self):
+        self.assertIsNone(self.router.allow_migrate("db", "models_", "plainmodel"))
+
+    def test_nonexistent_model(self):
+        self.assertIsNone(self.router.allow_migrate("db", "models_", "nonexistentmodel"))
+
+    def test_embedded_model(self):
+        self.assertIs(self.router.allow_migrate("db", "models_", "embed"), False)

From 18919da2af7af14f81372239e43b6b8e06b254c5 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Tue, 25 Feb 2025 11:06:25 -0500
Subject: [PATCH 21/25] Add release date for 5.0.0b1

---
 docs/source/releases/5.0.x.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/source/releases/5.0.x.rst b/docs/source/releases/5.0.x.rst
index 011647e73..c27c5f39f 100644
--- a/docs/source/releases/5.0.x.rst
+++ b/docs/source/releases/5.0.x.rst
@@ -5,7 +5,7 @@ Django MongoDB Backend 5.0.x
 5.0.0 beta 1
 ============
 
-*Unreleased*
+*February 26, 2025*
 
 - Backward-incompatible:
   :class:`~django_mongodb_backend.fields.ObjectIdAutoField` no longer accepts

From 653f400cf8f7eb719636a65cc2af733f8092885b Mon Sep 17 00:00:00 2001
From: "mongodb-dbx-release-bot[bot]"
 <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
Date: Thu, 27 Feb 2025 20:12:53 +0000
Subject: [PATCH 22/25] BUMP 5.0.0b1

Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com>
---
 django_mongodb_backend/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/django_mongodb_backend/__init__.py b/django_mongodb_backend/__init__.py
index f4b8fa405..f53f9b9c4 100644
--- a/django_mongodb_backend/__init__.py
+++ b/django_mongodb_backend/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "5.0.0b1.dev0"
+__version__ = "5.0.0b1"
 
 # Check Django compatibility before other imports which may fail if the
 # wrong version of Django is installed.

From 83c76a982bf33b8b6efdbf5c07e1ff37eff3ccf1 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Wed, 7 May 2025 10:34:47 -0400
Subject: [PATCH 23/25] Correct docs: QuerySet.bulk_update() and distinct() are
 supported

Added in 97769ba88e6951d8e343f8c3abeb8978d49b6f01
and e04056ed9ed5000c2427ecdef0db50a1649fe3a6.
---
 docs/source/ref/models/querysets.rst | 2 --
 docs/source/topics/known-issues.rst  | 2 --
 2 files changed, 4 deletions(-)

diff --git a/docs/source/ref/models/querysets.rst b/docs/source/ref/models/querysets.rst
index e8a7afc5a..843b72844 100644
--- a/docs/source/ref/models/querysets.rst
+++ b/docs/source/ref/models/querysets.rst
@@ -8,10 +8,8 @@ Supported ``QuerySet`` methods
 All of Django's :doc:`QuerySet methods <django:ref/models/querysets>` are
 supported, except:
 
-  - :meth:`bulk_update() <django.db.models.query.QuerySet.bulk_update>`
   - :meth:`dates() <django.db.models.query.QuerySet.dates>`
   - :meth:`datetimes() <django.db.models.query.QuerySet.datetimes>`
-  - :meth:`distinct() <django.db.models.query.QuerySet.distinct>`
   - :meth:`extra() <django.db.models.query.QuerySet.extra>`
   - :meth:`prefetch_related() <django.db.models.query.QuerySet.prefetch_related>`
 
diff --git a/docs/source/topics/known-issues.rst b/docs/source/topics/known-issues.rst
index 005727868..79eb2566b 100644
--- a/docs/source/topics/known-issues.rst
+++ b/docs/source/topics/known-issues.rst
@@ -30,10 +30,8 @@ Querying
 
 - The following ``QuerySet`` methods aren't supported:
 
-  - :meth:`bulk_update() <django.db.models.query.QuerySet.bulk_update>`
   - :meth:`dates() <django.db.models.query.QuerySet.dates>`
   - :meth:`datetimes() <django.db.models.query.QuerySet.datetimes>`
-  - :meth:`distinct() <django.db.models.query.QuerySet.distinct>`
   - :meth:`extra() <django.db.models.query.QuerySet.extra>`
   - :meth:`prefetch_related() <django.db.models.query.QuerySet.prefetch_related>`
 

From e01f4ca541cd1b7a49891c5f1f549c54671277b6 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Wed, 7 May 2025 10:54:56 -0400
Subject: [PATCH 24/25] Add "Known issues and limitations" link to the index

---
 docs/source/index.rst | 1 +
 1 file changed, 1 insertion(+)

diff --git a/docs/source/index.rst b/docs/source/index.rst
index b39f574bd..c38c25fbd 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -14,6 +14,7 @@ First steps
 - :doc:`Installation <intro/install>`
 - :doc:`Configuring a project <intro/configure>`
 - :doc:`howto/contrib-apps`
+- :doc:`topics/known-issues`
 
 Getting help
 ============

From 00242e6981e49b77ca05d94a798b38ff53a8e672 Mon Sep 17 00:00:00 2001
From: Steven Silvester <steve.silvester@mongodb.com>
Date: Thu, 24 Apr 2025 12:47:57 -0500
Subject: [PATCH 25/25] PYTHON-5348 Fix CodeQL Scanning for GitHub Actions
 (#294)

---
 .github/workflows/codeql.yml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 20a905fda..e2b43d624 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -64,7 +64,6 @@ jobs:
         queries: security-extended
         config: |
           paths-ignore:
-            - '.github/**'
             - 'tests/**'
 
     - shell: bash
