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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ max_supported_python = "3.14"
keep_full_version = true

[tool.pytest.ini_options]
addopts = "--tb=short --strict-markers -ra"
addopts = "--tb=short --strict-markers -ra --no-migrations"
testpaths = [ "tests" ]
markers = [
"requires_postgres: marks tests as requiring a PostgreSQL database backend",
Expand Down
13 changes: 12 additions & 1 deletion rest_framework/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.handlers.wsgi import WSGIHandler
from django.core.signals import request_finished, request_started
from django.db import close_old_connections
from django.test import override_settings, testcases
from django.test.client import Client as DjangoClient
from django.test.client import ClientHandler
Expand Down Expand Up @@ -89,8 +91,17 @@ def start_response(wsgi_status, wsgi_headers, exc_info=None):
raw_kwargs['original_response'] = MockOriginalResponse(wsgi_headers)

# Make the outgoing request via WSGI.
# Disconnect close_old_connections to prevent closing the
# database connection during tests, matching the behavior
# of Django's ClientHandler.
environ = self.get_environ(request)
wsgi_response = self.app(environ, start_response)
request_started.disconnect(close_old_connections)
request_finished.disconnect(close_old_connections)
try:
wsgi_response = self.app(environ, start_response)
finally:
request_started.connect(close_old_connections)
request_finished.connect(close_old_connections)

# Build the underlying urllib3.HTTPResponse
raw_kwargs['body'] = io.BytesIO(b''.join(wsgi_response))
Expand Down
139 changes: 73 additions & 66 deletions tests/test_filters.py

Large diffs are not rendered by default.

101 changes: 59 additions & 42 deletions tests/test_generics.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,8 @@ def test_post_root_view(self):
with self.assertNumQueries(1):
response = self.view(request).render()
assert response.status_code == status.HTTP_201_CREATED
assert response.data == {'id': 4, 'text': 'foobar'}
created = self.objects.get(id=4)
assert created.text == 'foobar'
created = self.objects.get(text='foobar')
assert response.data == {'id': created.pk, 'text': 'foobar'}

def test_put_root_view(self):
"""
Expand Down Expand Up @@ -153,9 +152,8 @@ def test_post_cannot_set_id(self):
with self.assertNumQueries(1):
response = self.view(request).render()
assert response.status_code == status.HTTP_201_CREATED
assert response.data == {'id': 4, 'text': 'foobar'}
created = self.objects.get(id=4)
assert created.text == 'foobar'
created = self.objects.get(text='foobar')
assert response.data == {'id': created.pk, 'text': 'foobar'}

def test_post_error_root_view(self):
"""
Expand All @@ -177,8 +175,11 @@ def setUp(self):
Create 3 BasicModel instances.
"""
items = ['foo', 'bar', 'baz', 'filtered out']
self.created_items = []
for item in items:
BasicModel(text=item).save()
obj = BasicModel.objects.create(text=item)
if item != 'filtered out':
self.created_items.append(obj)
self.objects = BasicModel.objects.exclude(text='filtered out')
self.data = [
{'id': obj.id, 'text': obj.text}
Expand All @@ -191,9 +192,10 @@ def test_get_instance_view(self):
"""
GET requests to RetrieveUpdateDestroyAPIView should return a single object.
"""
request = factory.get('/1')
pk = self.created_items[0].pk
request = factory.get(f'/{pk}')
with self.assertNumQueries(1):
response = self.view(request, pk=1).render()
response = self.view(request, pk=pk).render()
assert response.status_code == status.HTTP_200_OK
assert response.data == self.data[0]

Expand All @@ -212,40 +214,43 @@ def test_put_instance_view(self):
"""
PUT requests to RetrieveUpdateDestroyAPIView should update an object.
"""
pk = self.created_items[0].pk
data = {'text': 'foobar'}
request = factory.put('/1', data, format='json')
request = factory.put(f'/{pk}', data, format='json')
with self.assertNumQueries(EXPECTED_QUERIES_FOR_PUT):
response = self.view(request, pk='1').render()
response = self.view(request, pk=str(pk)).render()
assert response.status_code == status.HTTP_200_OK
assert dict(response.data) == {'id': 1, 'text': 'foobar'}
updated = self.objects.get(id=1)
assert dict(response.data) == {'id': pk, 'text': 'foobar'}
updated = self.objects.get(id=pk)
assert updated.text == 'foobar'

def test_patch_instance_view(self):
"""
PATCH requests to RetrieveUpdateDestroyAPIView should update an object.
"""
pk = self.created_items[0].pk
data = {'text': 'foobar'}
request = factory.patch('/1', data, format='json')
request = factory.patch(f'/{pk}', data, format='json')

with self.assertNumQueries(EXPECTED_QUERIES_FOR_PUT):
response = self.view(request, pk=1).render()
response = self.view(request, pk=pk).render()
assert response.status_code == status.HTTP_200_OK
assert response.data == {'id': 1, 'text': 'foobar'}
updated = self.objects.get(id=1)
assert response.data == {'id': pk, 'text': 'foobar'}
updated = self.objects.get(id=pk)
assert updated.text == 'foobar'

def test_delete_instance_view(self):
"""
DELETE requests to RetrieveUpdateDestroyAPIView should delete an object.
"""
request = factory.delete('/1')
pk = self.created_items[0].pk
request = factory.delete(f'/{pk}')
with self.assertNumQueries(2):
response = self.view(request, pk=1).render()
response = self.view(request, pk=pk).render()
assert response.status_code == status.HTTP_204_NO_CONTENT
assert response.content == b''
ids = [obj.id for obj in self.objects.all()]
assert ids == [2, 3]
assert ids == [self.created_items[1].pk, self.created_items[2].pk]

def test_get_instance_view_incorrect_arg(self):
"""
Expand All @@ -261,25 +266,27 @@ def test_put_cannot_set_id(self):
"""
PUT requests to create a new object should not be able to set the id.
"""
pk = self.created_items[0].pk
data = {'id': 999, 'text': 'foobar'}
request = factory.put('/1', data, format='json')
request = factory.put(f'/{pk}', data, format='json')
with self.assertNumQueries(EXPECTED_QUERIES_FOR_PUT):
response = self.view(request, pk=1).render()
response = self.view(request, pk=pk).render()
assert response.status_code == status.HTTP_200_OK
assert response.data == {'id': 1, 'text': 'foobar'}
updated = self.objects.get(id=1)
assert response.data == {'id': pk, 'text': 'foobar'}
updated = self.objects.get(id=pk)
assert updated.text == 'foobar'

def test_put_to_deleted_instance(self):
"""
PUT requests to RetrieveUpdateDestroyAPIView should return 404 if
an object does not currently exist.
"""
self.objects.get(id=1).delete()
pk = self.created_items[0].pk
self.objects.get(id=pk).delete()
data = {'text': 'foobar'}
request = factory.put('/1', data, format='json')
request = factory.put(f'/{pk}', data, format='json')
with self.assertNumQueries(1):
response = self.view(request, pk=1).render()
response = self.view(request, pk=pk).render()
assert response.status_code == status.HTTP_404_NOT_FOUND

def test_put_to_filtered_out_instance(self):
Expand All @@ -298,19 +305,21 @@ def test_patch_cannot_create_an_object(self):
PATCH requests should not be able to create objects.
"""
data = {'text': 'foobar'}
request = factory.patch('/999', data, format='json')
non_existent_pk = 999999
request = factory.patch(f'/{non_existent_pk}', data, format='json')
with self.assertNumQueries(1):
response = self.view(request, pk=999).render()
response = self.view(request, pk=non_existent_pk).render()
assert response.status_code == status.HTTP_404_NOT_FOUND
assert not self.objects.filter(id=999).exists()
assert not self.objects.filter(id=non_existent_pk).exists()

def test_put_error_instance_view(self):
"""
Incorrect PUT requests in HTML should include a form error.
"""
pk = self.created_items[0].pk
data = {'text': 'foobar' * 100}
request = factory.put('/', data, HTTP_ACCEPT='text/html')
response = self.view(request, pk=1).render()
response = self.view(request, pk=pk).render()
expected_error = '<span class="help-block">Ensure this field has no more than 100 characters.</span>'
assert expected_error in response.rendered_content.decode()

Expand Down Expand Up @@ -345,8 +354,10 @@ def setUp(self):
Create 3 BasicModel instances.
"""
items = ['foo', 'bar', 'baz']
self.created_items = []
for item in items:
BasicModel(text=item).save()
obj = BasicModel.objects.create(text=item)
self.created_items.append(obj)
self.objects = BasicModel.objects
self.data = [
{'id': obj.id, 'text': obj.text}
Expand All @@ -369,9 +380,10 @@ def test_overridden_get_object_view(self):
"""
GET requests to RetrieveUpdateDestroyAPIView should return a single object.
"""
request = factory.get('/1')
pk = self.created_items[0].pk
request = factory.get(f'/{pk}')
with self.assertNumQueries(1):
response = self.view(request, pk=1).render()
response = self.view(request, pk=pk).render()
assert response.status_code == status.HTTP_200_OK
assert response.data == self.data[0]

Expand Down Expand Up @@ -404,7 +416,7 @@ def test_create_model_with_auto_now_add_field(self):
request = factory.post('/', data, format='json')
response = self.view(request).render()
assert response.status_code == status.HTTP_201_CREATED
created = self.objects.get(id=1)
created = self.objects.get(content='foobar')
assert created.content == 'foobar'


Expand Down Expand Up @@ -483,8 +495,10 @@ def setUp(self):
Create 3 BasicModel instances to filter on.
"""
items = ['foo', 'bar', 'baz']
self.created_items = []
for item in items:
BasicModel(text=item).save()
obj = BasicModel.objects.create(text=item)
self.created_items.append(obj)
self.objects = BasicModel.objects
self.data = [
{'id': obj.id, 'text': obj.text}
Expand All @@ -500,7 +514,8 @@ def test_get_root_view_filters_by_name_with_filter_backend(self):
response = root_view(request).render()
assert response.status_code == status.HTTP_200_OK
assert len(response.data) == 1
assert response.data == [{'id': 1, 'text': 'foo'}]
foo_obj = self.created_items[0]
assert response.data == [{'id': foo_obj.pk, 'text': 'foo'}]

def test_get_root_view_filters_out_all_models_with_exclusive_filter_backend(self):
"""
Expand All @@ -516,9 +531,10 @@ def test_get_instance_view_filters_out_name_with_filter_backend(self):
"""
GET requests to RetrieveUpdateDestroyAPIView should raise 404 when model filtered out.
"""
pk = self.created_items[0].pk
instance_view = InstanceView.as_view(filter_backends=(ExclusiveFilterBackend,))
request = factory.get('/1')
response = instance_view(request, pk=1).render()
request = factory.get(f'/{pk}')
response = instance_view(request, pk=pk).render()
assert response.status_code == status.HTTP_404_NOT_FOUND
assert response.data == {
'detail': ErrorDetail(
Expand All @@ -531,11 +547,12 @@ def test_get_instance_view_will_return_single_object_when_filter_does_not_exclud
"""
GET requests to RetrieveUpdateDestroyAPIView should return a single object when not excluded
"""
foo_obj = self.created_items[0]
instance_view = InstanceView.as_view(filter_backends=(InclusiveFilterBackend,))
request = factory.get('/1')
response = instance_view(request, pk=1).render()
request = factory.get(f'/{foo_obj.pk}')
response = instance_view(request, pk=foo_obj.pk).render()
assert response.status_code == status.HTTP_200_OK
assert response.data == {'id': 1, 'text': 'foo'}
assert response.data == {'id': foo_obj.pk, 'text': 'foo'}

def test_dynamic_serializer_form_in_browsable_api(self):
"""
Expand Down
10 changes: 5 additions & 5 deletions tests/test_model_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ class DisplayValueModel(models.Model):

class TestRelationalFieldDisplayValue(TestCase):
def setUp(self):
DisplayValueTargetModel.objects.bulk_create([
self.targets = DisplayValueTargetModel.objects.bulk_create([
DisplayValueTargetModel(name='Red'),
DisplayValueTargetModel(name='Yellow'),
DisplayValueTargetModel(name='Green'),
Expand All @@ -773,7 +773,7 @@ class Meta:
fields = '__all__'

serializer = TestSerializer()
expected = {1: 'Red Color', 2: 'Yellow Color', 3: 'Green Color'}
expected = {t.pk: '%s Color' % t.name for t in self.targets}
self.assertEqual(serializer.fields['color'].choices, expected)

def test_custom_display_value(self):
Expand All @@ -789,7 +789,7 @@ class Meta:
fields = '__all__'

serializer = TestSerializer()
expected = {1: 'My Red Color', 2: 'My Yellow Color', 3: 'My Green Color'}
expected = {t.pk: 'My %s Color' % t.name for t in self.targets}
self.assertEqual(serializer.fields['color'].choices, expected)


Expand Down Expand Up @@ -1278,10 +1278,10 @@ class Meta:
parent_serializer = TestParentModelSerializer(parent)
child_serializer = TestChildModelSerializer(child)

parent_expected = {'children': ['def'], 'id': 1, 'title': 'abc'}
parent_expected = {'children': ['def'], 'id': parent.pk, 'title': 'abc'}
self.assertEqual(parent_serializer.data, parent_expected)

child_expected = {'parent': 1, 'value': 'def'}
child_expected = {'parent': parent.pk, 'value': 'def'}
self.assertEqual(child_serializer.data, child_expected)


Expand Down
Loading
Loading