Skip to content

Commit dfa0160

Browse files
authored
Merge branch 'main' into drop-django4.2
2 parents 2841bf9 + 7fe6124 commit dfa0160

22 files changed

Lines changed: 245 additions & 102 deletions

.github/workflows/main.yml

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
pre-commit:
1515
runs-on: ubuntu-latest
1616
steps:
17-
- uses: actions/checkout@v6
17+
- uses: actions/checkout@v6.0.3
1818
with:
1919
fetch-depth: 0
2020

@@ -56,7 +56,7 @@ jobs:
5656
DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
5757

5858
steps:
59-
- uses: actions/checkout@v6
59+
- uses: actions/checkout@v6.0.3
6060

6161
- uses: actions/setup-python@v6
6262
with:
@@ -70,9 +70,6 @@ jobs:
7070
- name: Install dependencies
7171
run: python -m pip install --upgrade tox
7272

73-
- name: Create unaccent extension
74-
run: PGPASSWORD=postgres psql -h localhost -U postgres -d postgres -c 'CREATE EXTENSION IF NOT EXISTS unaccent;'
75-
7673
- name: Run tox targets for ${{ matrix.python-version }}
7774
run: tox run -f py$(echo ${{ matrix.python-version }} | tr -d . | cut -f 1 -d '-')
7875

@@ -82,15 +79,15 @@ jobs:
8279
tox -e base,dist,docs
8380
8481
- name: Upload coverage
85-
uses: codecov/codecov-action@v6
82+
uses: codecov/codecov-action@v6.0.1
8683
with:
8784
env_vars: TOXENV,DJANGO
8885

8986
test-docs:
9087
name: Test documentation links
9188
runs-on: ubuntu-24.04
9289
steps:
93-
- uses: actions/checkout@v6
90+
- uses: actions/checkout@v6.0.3
9491

9592
- uses: actions/setup-python@v6
9693
with:

.github/workflows/mkdocs-deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
concurrency:
2121
group: ${{ github.workflow }}-${{ github.ref }}
2222
steps:
23-
- uses: actions/checkout@v6
23+
- uses: actions/checkout@v6.0.3
2424
- run: git fetch --no-tags --prune --depth=1 origin gh-pages
2525
- uses: actions/setup-python@v6
2626
with:

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
if: github.repository == 'encode/django-rest-framework'
2727
runs-on: ubuntu-24.04
2828
steps:
29-
- uses: actions/checkout@v6
29+
- uses: actions/checkout@v6.0.3
3030
- name: Set up Python
3131
uses: actions/setup-python@v6
3232
with:

docs/api-guide/requests.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ source:
55

66
> If you're doing REST-based web service stuff ... you should ignore request.POST.
77
>
8-
> — Malcom Tredinnick, [Django developers group][cite]
8+
> — Malcolm Tredinnick, [Django developers group][cite]
99
1010
REST framework's `Request` class extends the standard `HttpRequest`, adding support for REST framework's flexible request parsing and request authentication.
1111

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ max_supported_python = "3.14"
107107
keep_full_version = true
108108

109109
[tool.pytest.ini_options]
110-
addopts = "--tb=short --strict-markers -ra"
110+
addopts = "--tb=short --strict-markers -ra --no-migrations"
111111
testpaths = [ "tests" ]
112112
markers = [
113113
"requires_postgres: marks tests as requiring a PostgreSQL database backend",

rest_framework/fields.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -708,12 +708,13 @@ def to_internal_value(self, data):
708708
self.fail("invalid", input=data)
709709

710710
def to_representation(self, value):
711-
if self._lower_if_str(value) in self.TRUE_VALUES:
712-
return True
713-
elif self._lower_if_str(value) in self.FALSE_VALUES:
714-
return False
715-
if self._lower_if_str(value) in self.NULL_VALUES and self.allow_null:
716-
return None
711+
with contextlib.suppress(TypeError):
712+
if self._lower_if_str(value) in self.TRUE_VALUES:
713+
return True
714+
elif self._lower_if_str(value) in self.FALSE_VALUES:
715+
return False
716+
if self._lower_if_str(value) in self.NULL_VALUES and self.allow_null:
717+
return None
717718
return bool(value)
718719

719720

rest_framework/renderers.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,17 @@ def get_rendered_html_form(self, data, view, method, request):
496496
existing_serializer = None
497497

498498
with override_method(view, request, method) as request:
499+
if method == 'OPTIONS':
500+
# The browsable API only needs a placeholder for OPTIONS, so
501+
# avoid object-level permission checks against serializer.instance.
502+
if method not in view.allowed_methods:
503+
return
504+
try:
505+
view.check_permissions(request)
506+
except exceptions.APIException:
507+
return
508+
return True
509+
499510
if not self.show_form_for_method(view, method, request, instance):
500511
return
501512

rest_framework/test.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
# Note that we import as `DjangoRequestFactory` and `DjangoClient` in order
22
# to make it harder for the user to import the wrong thing without realizing.
33
import io
4+
from contextlib import contextmanager
45
from importlib import import_module
56

67
from django.conf import settings
78
from django.core.exceptions import ImproperlyConfigured
89
from django.core.handlers.wsgi import WSGIHandler
10+
from django.core.signals import request_finished, request_started
11+
from django.db import close_old_connections
912
from django.test import override_settings, testcases
1013
from django.test.client import Client as DjangoClient
1114
from django.test.client import ClientHandler
@@ -22,6 +25,21 @@ def force_authenticate(request, user=None, token=None):
2225
request._force_auth_token = token
2326

2427

28+
@contextmanager
29+
def _keep_connections_open():
30+
"""
31+
Prevent Django from closing the database connection while a request
32+
is dispatched, matching the behavior of Django's ClientHandler.
33+
"""
34+
request_started.disconnect(close_old_connections)
35+
request_finished.disconnect(close_old_connections)
36+
try:
37+
yield
38+
finally:
39+
request_started.connect(close_old_connections)
40+
request_finished.connect(close_old_connections)
41+
42+
2543
if requests is not None:
2644
class HeaderDict(requests.packages.urllib3._collections.HTTPHeaderDict):
2745
def get_all(self, key, default):
@@ -90,7 +108,8 @@ def start_response(wsgi_status, wsgi_headers, exc_info=None):
90108

91109
# Make the outgoing request via WSGI.
92110
environ = self.get_environ(request)
93-
wsgi_response = self.app(environ, start_response)
111+
with _keep_connections_open():
112+
wsgi_response = self.app(environ, start_response)
94113

95114
# Build the underlying urllib3.HTTPResponse
96115
raw_kwargs['body'] = io.BytesIO(b''.join(wsgi_response))

tests/conftest.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,31 @@
33
import dj_database_url
44
import django
55
import pytest
6+
from django.apps import apps
67
from django.core import management
8+
from django.core.management.color import no_style
9+
from django.db import connection
10+
11+
12+
@pytest.fixture
13+
def reset_sequences():
14+
"""
15+
Reset all database sequences so PKs start from 1.
16+
17+
PostgreSQL sequences are non-transactional and persist across
18+
TestCase's transaction rollbacks. Apply this fixture to test
19+
classes that rely on hardcoded PKs to keep them predictable
20+
regardless of execution order. No-op on SQLite.
21+
"""
22+
if connection.vendor != 'postgresql':
23+
return
24+
table_names = set(connection.introspection.table_names())
25+
models = [m for m in apps.get_models() if m._meta.db_table in table_names]
26+
sql_list = connection.ops.sequence_reset_sql(no_style(), models)
27+
if sql_list:
28+
with connection.cursor() as cursor:
29+
for sql in sql_list:
30+
cursor.execute(sql)
731

832

933
def pytest_addoption(parser):

tests/test_fields.py

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -772,33 +772,38 @@ class TestBooleanField(FieldValues):
772772
'foo': ['Must be a valid boolean.'],
773773
None: ['This field may not be null.']
774774
}
775-
outputs = {
776-
'True': True,
777-
'TRUE': True,
778-
'tRuE': True,
779-
't': True,
780-
'T': True,
781-
'true': True,
782-
'on': True,
783-
'ON': True,
784-
'oN': True,
785-
'False': False,
786-
'FALSE': False,
787-
'fALse': False,
788-
'f': False,
789-
'F': False,
790-
'false': False,
791-
'off': False,
792-
'OFF': False,
793-
'oFf': False,
794-
'1': True,
795-
'0': False,
796-
1: True,
797-
0: False,
798-
True: True,
799-
False: False,
800-
'other': True
801-
}
775+
outputs = [
776+
('True', True),
777+
('TRUE', True),
778+
('tRuE', True),
779+
('t', True),
780+
('T', True),
781+
('true', True),
782+
('on', True),
783+
('ON', True),
784+
('oN', True),
785+
('False', False),
786+
('FALSE', False),
787+
('fALse', False),
788+
('f', False),
789+
('F', False),
790+
('false', False),
791+
('off', False),
792+
('OFF', False),
793+
('oFf', False),
794+
('1', True),
795+
('0', False),
796+
(1, True),
797+
(0, False),
798+
(True, True),
799+
(False, False),
800+
('other', True),
801+
([], False),
802+
({}, False),
803+
(set(), False),
804+
([1], True),
805+
({'example': 'value'}, True),
806+
]
802807
field = serializers.BooleanField()
803808

804809
def test_disallow_unhashable_collection_types(self):

0 commit comments

Comments
 (0)