Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions django/db/backends/base/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -886,3 +886,25 @@ def compile_json_path(self, key_transforms, include_root=True):
)
path.append(self.format_json_path_numeric_index(num))
return "".join(path)

def get_hardcoded_pk(self, value):
"""
Given an integer suggestion (which works for built-in backends because
they don't override DEFAULT_AUTO_FIELD), return a hardcoded primary key
value for use in tests.

Example use case: MongoDB uses DEFAULT_AUTO_FIELD=ObjectIdAutoField and
an integer pk can be converted to bson.ObjectId().
"""
return value

def get_nonexistent_pk(self, value):
"""
Given the last created pk (an integer for the built-in backends which
use DEFAULT_AUTO_FIELD=BigAutoField), return a nonexistent primary key
value for use in tests.

Example use case: MongoDB uses DEFAULT_AUTO_FIELD=ObjectIdAutoField and
can use ObjectId() to generate a nonexistent ID.
"""
return value + 1
28 changes: 18 additions & 10 deletions tests/admin_changelist/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,8 @@ def test_pk_in_search_fields(self):
cl = m.get_changelist_instance(request)
self.assertEqual(cl.queryset.count(), 1)

request = self.factory.get("/concert/", data={SEARCH_VAR: band.pk + 5})
nonexistent_pk = connection.ops.get_nonexistent_pk(band.pk)
request = self.factory.get("/concert/", data={SEARCH_VAR: nonexistent_pk})
request.user = self.superuser
cl = m.get_changelist_instance(request)
self.assertEqual(cl.queryset.count(), 0)
Expand Down Expand Up @@ -996,7 +997,8 @@ def test_no_distinct_for_m2m_in_list_filter_without_params(self):
self.assertIs(cl.queryset.query.distinct, False)

# A ManyToManyField in params does have distinct applied.
request = self.factory.get("/band/", {"genres": "0"})
pk_value = str(connection.ops.get_hardcoded_pk(0))
request = self.factory.get("/band/", {"genres": pk_value})
request.user = self.superuser
cl = m.get_changelist_instance(request)
self.assertIs(cl.queryset.query.distinct, True)
Expand Down Expand Up @@ -1148,16 +1150,20 @@ def test_dynamic_list_display_links(self):
Regression tests for #16257: dynamic list_display_links support.
"""
parent = Parent.objects.create(name="parent")
children = []
for i in range(1, 10):
Child.objects.create(id=i, name="child %s" % i, parent=parent, age=i)
children.append(
Child.objects.create(name="child %s" % i, parent=parent, age=i + 1)
)

m = DynamicListDisplayLinksChildAdmin(Child, custom_site)
superuser = self._create_superuser("superuser")
request = self._mocked_authenticated_request("/child/", superuser)
response = m.changelist_view(request)
for i in range(1, 10):
link = reverse("admin:admin_changelist_child_change", args=(i,))
self.assertContains(response, '<a href="%s">%s</a>' % (link, i))
for child in children:
link = reverse("admin:admin_changelist_child_change", args=(child.pk,))
# "age" is in list_display_links.
self.assertContains(response, '<a href="%s">%s</a>' % (link, child.age))

list_display = m.get_list_display(request)
list_display_links = m.get_list_display_links(request, list_display)
Expand Down Expand Up @@ -1469,9 +1475,10 @@ def test_deterministic_order_for_unordered_model(self):
default ordering defined (#17198).
"""
superuser = self._create_superuser("superuser")
pk = connection.ops.get_hardcoded_pk

for counter in range(1, 51):
UnorderedObject.objects.create(id=counter, bool=True)
UnorderedObject.objects.create(id=pk(counter), bool=True)

class UnorderedObjectAdmin(admin.ModelAdmin):
list_per_page = 10
Expand All @@ -1487,7 +1494,7 @@ def check_results_order(ascending=False):
response = model_admin.changelist_view(request)
for result in response.context_data["cl"].result_list:
counter += 1 if ascending else -1
self.assertEqual(result.id, counter)
self.assertEqual(result.id, pk(counter))
custom_site.unregister(UnorderedObject)

# When no order is defined at all, everything is ordered by '-pk'.
Expand Down Expand Up @@ -1530,9 +1537,10 @@ def test_deterministic_order_for_model_ordered_by_its_manager(self):
defines a default ordering (#17198).
"""
superuser = self._create_superuser("superuser")
pk = connection.ops.get_hardcoded_pk

for counter in range(1, 51):
OrderedObject.objects.create(id=counter, bool=True, number=counter)
OrderedObject.objects.create(id=pk(counter), bool=True, number=counter)

class OrderedObjectAdmin(admin.ModelAdmin):
list_per_page = 10
Expand All @@ -1548,7 +1556,7 @@ def check_results_order(ascending=False):
response = model_admin.changelist_view(request)
for result in response.context_data["cl"].result_list:
counter += 1 if ascending else -1
self.assertEqual(result.id, counter)
self.assertEqual(result.id, pk(counter))
custom_site.unregister(OrderedObject)

# When no order is defined at all, use the model's default ordering
Expand Down
14 changes: 11 additions & 3 deletions tests/admin_inlines/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.contrib.admin.tests import AdminSeleniumTestCase
from django.contrib.auth.models import Permission, User
from django.contrib.contenttypes.models import ContentType
from django.db import connection
from django.test import RequestFactory, TestCase, override_settings
from django.test.selenium import screenshot_cases
from django.urls import reverse
Expand Down Expand Up @@ -525,8 +526,15 @@ def test_localize_pk_shortcut(self):
The "View on Site" link is correct for locales that use thousand
separators.
"""
holder = Holder.objects.create(pk=123456789, dummy=42)
inner = Inner.objects.create(pk=987654321, holder=holder, dummy=42, readonly="")
holder = Holder.objects.create(
pk=connection.ops.get_hardcoded_pk(123456789), dummy=42
)
inner = Inner.objects.create(
pk=connection.ops.get_hardcoded_pk(987654321),
holder=holder,
dummy=42,
readonly="",
)
response = self.client.get(
reverse("admin:admin_inlines_holder_change", args=(holder.id,))
)
Expand Down Expand Up @@ -960,7 +968,7 @@ def setUpTestData(cls):
)
cls.user.user_permissions.add(permission)

author = Author.objects.create(pk=1, name="The Author")
author = Author.objects.create(name="The Author")
cls.book = author.books.create(name="The inline Book")
cls.author_change_url = reverse(
"admin:admin_inlines_author_change", args=(author.id,)
Expand Down
5 changes: 3 additions & 2 deletions tests/admin_views/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError
from django.core.mail import EmailMessage
from django.db import models
from django.db import connection, models
from django.forms.models import BaseModelFormSet
from django.http import HttpResponse, JsonResponse, StreamingHttpResponse
from django.urls import path
Expand Down Expand Up @@ -722,7 +722,8 @@ class FieldOverridePostAdmin(PostAdmin):

class CustomChangeList(ChangeList):
def get_queryset(self, request):
return self.root_queryset.order_by("pk").filter(pk=9999) # Doesn't exist
nonexistent_pk = connection.ops.get_nonexistent_pk(9999)
return self.root_queryset.order_by("pk").filter(pk=nonexistent_pk)


class GadgetAdmin(admin.ModelAdmin):
Expand Down
9 changes: 5 additions & 4 deletions tests/admin_views/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,10 @@ def test_model_admin_default_delete_action(self):
self.assertEqual(Subscriber.objects.count(), 0)

def test_default_delete_action_nonexistent_pk(self):
self.assertFalse(Subscriber.objects.filter(id=9998).exists())
nonexistent_pk = connection.ops.get_nonexistent_pk(9998)
self.assertFalse(Subscriber.objects.filter(id=nonexistent_pk).exists())
action_data = {
ACTION_CHECKBOX_NAME: ["9998"],
ACTION_CHECKBOX_NAME: [str(nonexistent_pk)],
"action": "delete_selected",
"index": 0,
}
Expand All @@ -123,7 +124,7 @@ def test_non_localized_pk(self):
If USE_THOUSAND_SEPARATOR is set, the ids for the objects selected for
deletion are rendered without separators.
"""
s = ExternalSubscriber.objects.create(id=9999)
s = ExternalSubscriber.objects.create(id=connection.ops.get_hardcoded_pk(9999))
action_data = {
ACTION_CHECKBOX_NAME: [s.pk, self.s2.pk],
"action": "delete_selected",
Expand All @@ -133,7 +134,7 @@ def test_non_localized_pk(self):
reverse("admin:admin_views_subscriber_changelist"), action_data
)
self.assertTemplateUsed(response, "admin/delete_selected_confirmation.html")
self.assertContains(response, 'value="9999"') # Instead of 9,999
self.assertContains(response, f'value="{s.pk}"') # Instead of 9,999
self.assertContains(response, 'value="%s"' % self.s2.pk)

def test_model_admin_default_delete_action_protected(self):
Expand Down
Loading