From 7b4b2f285c51fa2cc12e5801be768d8245fbcb66 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 7 Apr 2026 15:42:09 -0300 Subject: [PATCH 01/12] Pass DATABASE_URL env variable in tox --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 00e5bd57b8..63e075c1c1 100644 --- a/tox.ini +++ b/tox.ini @@ -15,6 +15,8 @@ envdir = {toxworkdir}/venvs/{envname} setenv = PYTHONDONTWRITEBYTECODE=1 PYTHONWARNINGS=once +pass_env = + DATABASE_URL dependency_groups = test optional From 85ea3cb908a36cd1a5eb06fcdca289dd8e1dab4e Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 7 Apr 2026 15:58:09 -0300 Subject: [PATCH 02/12] Disable migrations in tests --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bf5ce6ffa1..13e2338eca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", From ee1164c28d54f51816afc26f2641e8b8000e5023 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 7 Apr 2026 16:01:16 -0300 Subject: [PATCH 03/12] Fix model --- tests/test_filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_filters.py b/tests/test_filters.py index f797a01eb7..e2c7cd588d 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -45,7 +45,7 @@ def test_filter_queryset_raises_error(self): class SearchFilterModel(models.Model): - title = models.CharField(max_length=20) + title = models.CharField(max_length=22) text = models.CharField(max_length=100) From e6b6e98a575f42ea48eb74764757fd2712155066 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 7 Apr 2026 16:42:09 -0300 Subject: [PATCH 04/12] Update tests to pass on PostgreSQL --- rest_framework/test.py | 13 +- tests/test_filters.py | 137 ++++++++------- tests/test_generics.py | 101 ++++++----- tests/test_model_serializer.py | 10 +- tests/test_permissions.py | 104 ++++++------ tests/test_prefetch_related.py | 4 +- tests/test_relations_hyperlink.py | 271 ++++++++++++++++++------------ tests/test_relations_pk.py | 265 +++++++++++++++-------------- tests/test_relations_slug.py | 164 +++++++++--------- tests/test_validators.py | 6 +- 10 files changed, 588 insertions(+), 487 deletions(-) diff --git a/rest_framework/test.py b/rest_framework/test.py index ae347ec002..9f6aab22bf 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -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 @@ -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)) diff --git a/tests/test_filters.py b/tests/test_filters.py index e2c7cd588d..8bae8d30fe 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -64,6 +64,7 @@ def setUpTestData(cls): # zz bcd # zzz cde # ... + cls.objects_by_title = {} for idx in range(10): title = 'z' * (idx + 1) text = ( @@ -71,10 +72,11 @@ def setUpTestData(cls): chr(idx + ord('b')) + chr(idx + ord('c')) ) - SearchFilterModel(title=title, text=text).save() + obj = SearchFilterModel.objects.create(title=title, text=text) + cls.objects_by_title[title] = obj - SearchFilterModel(title='A title', text='The long text').save() - SearchFilterModel(title='The title', text='The "text').save() + cls.obj_a_title = SearchFilterModel.objects.create(title='A title', text='The long text') + cls.obj_the_title = SearchFilterModel.objects.create(title='The title', text='The "text') def test_search(self): class SearchListView(generics.ListAPIView): @@ -87,8 +89,8 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': 'b'}) response = view(request) assert response.data == [ - {'id': 1, 'title': 'z', 'text': 'abc'}, - {'id': 2, 'title': 'zz', 'text': 'bcd'} + {'id': self.objects_by_title['z'].pk, 'title': 'z', 'text': 'abc'}, + {'id': self.objects_by_title['zz'].pk, 'title': 'zz', 'text': 'bcd'} ] def test_search_returns_same_queryset_if_no_search_fields_or_terms_provided(self): @@ -115,7 +117,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': 'zzz'}) response = view(request) assert response.data == [ - {'id': 3, 'title': 'zzz', 'text': 'cde'} + {'id': self.objects_by_title['zzz'].pk, 'title': 'zzz', 'text': 'cde'} ] def test_startswith_search(self): @@ -129,7 +131,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': 'b'}) response = view(request) assert response.data == [ - {'id': 2, 'title': 'zz', 'text': 'bcd'} + {'id': self.objects_by_title['zz'].pk, 'title': 'zz', 'text': 'bcd'} ] def test_regexp_search(self): @@ -143,7 +145,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': 'z{2} ^b'}) response = view(request) assert response.data == [ - {'id': 2, 'title': 'zz', 'text': 'bcd'} + {'id': self.objects_by_title['zz'].pk, 'title': 'zz', 'text': 'bcd'} ] def test_search_with_nonstandard_search_param(self): @@ -160,8 +162,8 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'query': 'b'}) response = view(request) assert response.data == [ - {'id': 1, 'title': 'z', 'text': 'abc'}, - {'id': 2, 'title': 'zz', 'text': 'bcd'} + {'id': self.objects_by_title['z'].pk, 'title': 'z', 'text': 'abc'}, + {'id': self.objects_by_title['zz'].pk, 'title': 'zz', 'text': 'bcd'} ] reload_module(filters) @@ -188,7 +190,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': r'^\w{3}$', 'title_only': 'true'}) response = view(request) assert response.data == [ - {'id': 3, 'title': 'zzz', 'text': 'cde'} + {'id': self.objects_by_title['zzz'].pk, 'title': 'zzz', 'text': 'cde'} ] def test_search_field_with_null_characters(self): @@ -209,7 +211,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': 'c'}) response = view(request) assert response.data == [ - {'id': 1, 'title': 'z', 'text': 'abc'}, + {'id': self.objects_by_title['z'].pk, 'title': 'z', 'text': 'abc'}, ] def test_search_field_with_additional_transforms(self): @@ -243,8 +245,8 @@ def as_sql(self, compiler, connection): request = factory.get('/', {'search': 'bc'}) response = view(request) assert response.data == [ - {'id': 1, 'title': 'z', 'text': 'abc'}, - {'id': 2, 'title': 'zz', 'text': 'bcd'}, + {'id': self.objects_by_title['z'].pk, 'title': 'z', 'text': 'abc'}, + {'id': self.objects_by_title['zz'].pk, 'title': 'zz', 'text': 'bcd'}, ] def test_search_field_with_multiple_words(self): @@ -274,7 +276,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': '"\\\"text"'}) response = view(request) assert response.data == [ - {'id': 12, 'title': 'The title', 'text': 'The "text'}, + {'id': self.obj_the_title.pk, 'title': 'The title', 'text': 'The "text'}, ] def test_search_field_with_quotes(self): @@ -287,7 +289,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': '"long text"'}) response = view(request) assert response.data == [ - {'id': 11, 'title': 'A title', 'text': 'The long text'}, + {'id': self.obj_a_title.pk, 'title': 'A title', 'text': 'The long text'}, ] @@ -463,13 +465,14 @@ class SearchFilterM2MTests(TestCase): def setUp(self): # Sequence of title/text/attributes is: # - # z abc [1, 2, 3] - # zz bcd [1, 2, 3] - # zzz cde [1, 2, 3] + # z abc [attr1, attr2, attr3] + # zz bcd [attr1, attr2, attr3] + # zzz cde [attr1, attr2, attr3] # ... + self.attributes = [] for idx in range(3): label = 'w' * (idx + 1) - AttributeModel.objects.create(label=label) + self.attributes.append(AttributeModel.objects.create(label=label)) for idx in range(10): title = 'z' * (idx + 1) @@ -479,7 +482,7 @@ def setUp(self): chr(idx + ord('c')) ) SearchFilterModelM2M(title=title, text=text).save() - SearchFilterModelM2M.objects.get(title='zz').attributes.add(1, 2, 3) + SearchFilterModelM2M.objects.get(title='zz').attributes.add(*self.attributes) def test_m2m_search(self): class SearchListView(generics.ListAPIView): @@ -664,6 +667,7 @@ def setUp(self): # zyx abc # yxw bcd # xwv cde + self.objects_by_title = {} for idx in range(3): title = ( chr(ord('z') - idx) + @@ -675,7 +679,8 @@ def setUp(self): chr(idx + ord('b')) + chr(idx + ord('c')) ) - OrderingFilterModel(title=title, text=text).save() + obj = OrderingFilterModel.objects.create(title=title, text=text) + self.objects_by_title[title] = obj def test_ordering(self): class OrderingListView(generics.ListAPIView): @@ -689,9 +694,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'ordering': 'text'}) response = view(request) assert response.data == [ - {'id': 1, 'title': 'zyx', 'text': 'abc'}, - {'id': 2, 'title': 'yxw', 'text': 'bcd'}, - {'id': 3, 'title': 'xwv', 'text': 'cde'}, + {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, + {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, + {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, ] def test_reverse_ordering(self): @@ -706,9 +711,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'ordering': '-text'}) response = view(request) assert response.data == [ - {'id': 3, 'title': 'xwv', 'text': 'cde'}, - {'id': 2, 'title': 'yxw', 'text': 'bcd'}, - {'id': 1, 'title': 'zyx', 'text': 'abc'}, + {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, + {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, + {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, ] def test_incorrecturl_extrahyphens_ordering(self): @@ -723,9 +728,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'ordering': '--text'}) response = view(request) assert response.data == [ - {'id': 3, 'title': 'xwv', 'text': 'cde'}, - {'id': 2, 'title': 'yxw', 'text': 'bcd'}, - {'id': 1, 'title': 'zyx', 'text': 'abc'}, + {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, + {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, + {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, ] def test_incorrectfield_ordering(self): @@ -740,9 +745,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'ordering': 'foobar'}) response = view(request) assert response.data == [ - {'id': 3, 'title': 'xwv', 'text': 'cde'}, - {'id': 2, 'title': 'yxw', 'text': 'bcd'}, - {'id': 1, 'title': 'zyx', 'text': 'abc'}, + {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, + {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, + {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, ] def test_ordering_without_ordering_fields(self): @@ -758,27 +763,27 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'ordering': 'text'}) response = view(request) assert response.data == [ - {'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, - {'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, - {'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, + {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, + {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, + {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, ] # `incorrectfield` ordering works fine. request = factory.get('/', {'ordering': 'foobar'}) response = view(request) assert response.data == [ - {'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, - {'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, - {'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, + {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, + {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, + {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, ] # `description` is a Model property, which should be ignored. request = factory.get('/', {'ordering': 'description'}) response = view(request) assert response.data == [ - {'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, - {'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, - {'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, + {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, + {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, + {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, ] def test_default_ordering(self): @@ -793,9 +798,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('') response = view(request) assert response.data == [ - {'id': 3, 'title': 'xwv', 'text': 'cde'}, - {'id': 2, 'title': 'yxw', 'text': 'bcd'}, - {'id': 1, 'title': 'zyx', 'text': 'abc'}, + {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, + {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, + {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, ] def test_default_ordering_using_string(self): @@ -810,9 +815,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('') response = view(request) assert response.data == [ - {'id': 3, 'title': 'xwv', 'text': 'cde'}, - {'id': 2, 'title': 'yxw', 'text': 'bcd'}, - {'id': 1, 'title': 'zyx', 'text': 'abc'}, + {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, + {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, + {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, ] def test_ordering_by_aggregate_field(self): @@ -838,9 +843,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'ordering': 'related__count'}) response = view(request) assert response.data == [ - {'id': 1, 'title': 'zyx', 'text': 'abc'}, - {'id': 3, 'title': 'xwv', 'text': 'cde'}, - {'id': 2, 'title': 'yxw', 'text': 'bcd'}, + {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, + {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, + {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, ] def test_ordering_by_dotted_source(self): @@ -888,9 +893,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'order': 'text'}) response = view(request) assert response.data == [ - {'id': 1, 'title': 'zyx', 'text': 'abc'}, - {'id': 2, 'title': 'yxw', 'text': 'bcd'}, - {'id': 3, 'title': 'xwv', 'text': 'cde'}, + {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, + {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, + {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, ] reload_module(filters) @@ -923,9 +928,9 @@ def get_serializer_class(self): request = factory.get('/', {'ordering': 'text'}) response = view(request) assert response.data == [ - {'id': 1, 'title': 'zyx', 'text': 'abc'}, - {'id': 2, 'title': 'yxw', 'text': 'bcd'}, - {'id': 3, 'title': 'xwv', 'text': 'cde'}, + {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, + {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, + {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, ] def test_ordering_with_improper_configuration(self): @@ -976,10 +981,12 @@ class Meta: class SensitiveOrderingFilterTests(TestCase): def setUp(self): + self.objects_by_username = {} for idx in range(3): username = {0: 'userA', 1: 'userB', 2: 'userC'}[idx] password = {0: 'passA', 1: 'passC', 2: 'passB'}[idx] - SensitiveOrderingFilterModel(username=username, password=password).save() + obj = SensitiveOrderingFilterModel.objects.create(username=username, password=password) + self.objects_by_username[username] = obj def test_order_by_serializer_fields(self): for serializer_cls in [ @@ -1003,9 +1010,9 @@ class OrderingListView(generics.ListAPIView): # Note: Inverse username ordering correctly applied. assert response.data == [ - {'id': 3, username_field: 'userC'}, - {'id': 2, username_field: 'userB'}, - {'id': 1, username_field: 'userA'}, + {'id': self.objects_by_username['userC'].pk, username_field: 'userC'}, + {'id': self.objects_by_username['userB'].pk, username_field: 'userB'}, + {'id': self.objects_by_username['userA'].pk, username_field: 'userA'}, ] def test_cannot_order_by_non_serializer_fields(self): @@ -1030,7 +1037,7 @@ class OrderingListView(generics.ListAPIView): # Note: The passwords are not in order. Default ordering is used. assert response.data == [ - {'id': 1, username_field: 'userA'}, # PassB - {'id': 2, username_field: 'userB'}, # PassC - {'id': 3, username_field: 'userC'}, # PassA + {'id': self.objects_by_username['userA'].pk, username_field: 'userA'}, # PassB + {'id': self.objects_by_username['userB'].pk, username_field: 'userB'}, # PassC + {'id': self.objects_by_username['userC'].pk, username_field: 'userC'}, # PassA ] diff --git a/tests/test_generics.py b/tests/test_generics.py index 25b96fcbb2..e8a1c142ff 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -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): """ @@ -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): """ @@ -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} @@ -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] @@ -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): """ @@ -261,13 +266,14 @@ 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): @@ -275,11 +281,12 @@ 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): @@ -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 = 'Ensure this field has no more than 100 characters.' assert expected_error in response.rendered_content.decode() @@ -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} @@ -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] @@ -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' @@ -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} @@ -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): """ @@ -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( @@ -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): """ diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 944c1ba278..702cabdd89 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -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'), @@ -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): @@ -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) @@ -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) diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 93fe7b9410..652cf6162a 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -92,12 +92,12 @@ def setUp(self): self.disallowed_credentials = basic_auth_header('disallowed', 'password') self.updateonly_credentials = basic_auth_header('updateonly', 'password') - BasicModel(text='foo').save() + self.instance = BasicModel.objects.create(text='foo') def test_has_create_permissions(self): request = factory.post('/', {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.permitted_credentials) - response = root_view(request, pk=1) + response = root_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_api_root_view_discard_default_django_model_permission(self): @@ -136,35 +136,35 @@ def test_ignore_model_permissions_with_authenticated_user(self): def test_get_queryset_has_create_permissions(self): request = factory.post('/', {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.permitted_credentials) - response = get_queryset_list_view(request, pk=1) + response = get_queryset_list_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_has_put_permissions(self): - request = factory.put('/1', {'text': 'foobar'}, format='json', + request = factory.put('/%d' % self.instance.pk, {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.permitted_credentials) - response = instance_view(request, pk='1') + response = instance_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_200_OK) def test_has_delete_permissions(self): - request = factory.delete('/1', HTTP_AUTHORIZATION=self.permitted_credentials) - response = instance_view(request, pk=1) + request = factory.delete('/%d' % self.instance.pk, HTTP_AUTHORIZATION=self.permitted_credentials) + response = instance_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) def test_does_not_have_create_permissions(self): request = factory.post('/', {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.disallowed_credentials) - response = root_view(request, pk=1) + response = root_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_does_not_have_put_permissions(self): - request = factory.put('/1', {'text': 'foobar'}, format='json', + request = factory.put('/%d' % self.instance.pk, {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.disallowed_credentials) - response = instance_view(request, pk='1') + response = instance_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_does_not_have_delete_permissions(self): - request = factory.delete('/1', HTTP_AUTHORIZATION=self.disallowed_credentials) - response = instance_view(request, pk=1) + request = factory.delete('/%d' % self.instance.pk, HTTP_AUTHORIZATION=self.disallowed_credentials) + response = instance_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_options_permitted(self): @@ -172,16 +172,16 @@ def test_options_permitted(self): '/', HTTP_AUTHORIZATION=self.permitted_credentials ) - response = root_view(request, pk='1') + response = root_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('actions', response.data) self.assertEqual(list(response.data['actions']), ['POST']) request = factory.options( - '/1', + '/%d' % self.instance.pk, HTTP_AUTHORIZATION=self.permitted_credentials ) - response = instance_view(request, pk='1') + response = instance_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('actions', response.data) self.assertEqual(list(response.data['actions']), ['PUT']) @@ -191,15 +191,15 @@ def test_options_disallowed(self): '/', HTTP_AUTHORIZATION=self.disallowed_credentials ) - response = root_view(request, pk='1') + response = root_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertNotIn('actions', response.data) request = factory.options( - '/1', + '/%d' % self.instance.pk, HTTP_AUTHORIZATION=self.disallowed_credentials ) - response = instance_view(request, pk='1') + response = instance_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertNotIn('actions', response.data) @@ -208,22 +208,22 @@ def test_options_updateonly(self): '/', HTTP_AUTHORIZATION=self.updateonly_credentials ) - response = root_view(request, pk='1') + response = root_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertNotIn('actions', response.data) request = factory.options( - '/1', + '/%d' % self.instance.pk, HTTP_AUTHORIZATION=self.updateonly_credentials ) - response = instance_view(request, pk='1') + response = instance_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('actions', response.data) self.assertEqual(list(response.data['actions']), ['PUT']) def test_empty_view_does_not_assert(self): - request = factory.get('/1', HTTP_AUTHORIZATION=self.permitted_credentials) - response = empty_list_view(request, pk=1) + request = factory.get('/%d' % self.instance.pk, HTTP_AUTHORIZATION=self.permitted_credentials) + response = empty_list_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_200_OK) def test_calling_method_not_allowed(self): @@ -231,8 +231,8 @@ def test_calling_method_not_allowed(self): response = root_view(request) self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) - request = factory.generic('METHOD_NOT_ALLOWED', '/1', HTTP_AUTHORIZATION=self.permitted_credentials) - response = instance_view(request, pk='1') + request = factory.generic('METHOD_NOT_ALLOWED', '/%d' % self.instance.pk, HTTP_AUTHORIZATION=self.permitted_credentials) + response = instance_view(request, pk=self.instance.pk) self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) def test_check_auth_before_queryset_call(self): @@ -362,11 +362,11 @@ def setUp(self): writers = Group.objects.create(name='writers') deleters = Group.objects.create(name='deleters') - model = BasicPermModel.objects.create(text='foo') + self.model = BasicPermModel.objects.create(text='foo') - assign_perm(perms['view'], readers, model) - assign_perm(perms['change'], writers, model) - assign_perm(perms['delete'], deleters, model) + assign_perm(perms['view'], readers, self.model) + assign_perm(perms['change'], writers, self.model) + assign_perm(perms['delete'], deleters, self.model) readers.user_set.add(users['fullaccess'], users['readonly']) writers.user_set.add(users['fullaccess'], users['writeonly']) @@ -378,50 +378,50 @@ def setUp(self): # Delete def test_can_delete_permissions(self): - request = factory.delete('/1', HTTP_AUTHORIZATION=self.credentials['deleteonly']) - response = object_permissions_view(request, pk='1') + request = factory.delete('/%d' % self.model.pk, HTTP_AUTHORIZATION=self.credentials['deleteonly']) + response = object_permissions_view(request, pk=self.model.pk) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) def test_cannot_delete_permissions(self): - request = factory.delete('/1', HTTP_AUTHORIZATION=self.credentials['readonly']) - response = object_permissions_view(request, pk='1') + request = factory.delete('/%d' % self.model.pk, HTTP_AUTHORIZATION=self.credentials['readonly']) + response = object_permissions_view(request, pk=self.model.pk) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) # Update def test_can_update_permissions(self): request = factory.patch( - '/1', {'text': 'foobar'}, format='json', + '/%d' % self.model.pk, {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.credentials['writeonly'] ) - response = object_permissions_view(request, pk='1') + response = object_permissions_view(request, pk=self.model.pk) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data.get('text'), 'foobar') def test_cannot_update_permissions(self): request = factory.patch( - '/1', {'text': 'foobar'}, format='json', + '/%d' % self.model.pk, {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.credentials['deleteonly'] ) - response = object_permissions_view(request, pk='1') + response = object_permissions_view(request, pk=self.model.pk) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_cannot_update_permissions_non_existing(self): request = factory.patch( - '/999', {'text': 'foobar'}, format='json', + '/999999', {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.credentials['deleteonly'] ) - response = object_permissions_view(request, pk='999') + response = object_permissions_view(request, pk='999999') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) # Read def test_can_read_permissions(self): - request = factory.get('/1', HTTP_AUTHORIZATION=self.credentials['readonly']) - response = object_permissions_view(request, pk='1') + request = factory.get('/%d' % self.model.pk, HTTP_AUTHORIZATION=self.credentials['readonly']) + response = object_permissions_view(request, pk=self.model.pk) self.assertEqual(response.status_code, status.HTTP_200_OK) def test_cannot_read_permissions(self): - request = factory.get('/1', HTTP_AUTHORIZATION=self.credentials['writeonly']) - response = object_permissions_view(request, pk='1') + request = factory.get('/%d' % self.model.pk, HTTP_AUTHORIZATION=self.credentials['writeonly']) + response = object_permissions_view(request, pk=self.model.pk) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_can_read_get_queryset_permissions(self): @@ -429,8 +429,8 @@ def test_can_read_get_queryset_permissions(self): same as ``test_can_read_permissions`` but with a view that rely on ``.get_queryset()`` instead of ``.queryset``. """ - request = factory.get('/1', HTTP_AUTHORIZATION=self.credentials['readonly']) - response = get_queryset_object_permissions_view(request, pk='1') + request = factory.get('/%d' % self.model.pk, HTTP_AUTHORIZATION=self.credentials['readonly']) + response = get_queryset_object_permissions_view(request, pk=self.model.pk) self.assertEqual(response.status_code, status.HTTP_200_OK) # Read list @@ -440,7 +440,7 @@ def test_can_read_list_permissions(self): request = factory.get('/', HTTP_AUTHORIZATION=self.credentials['readonly']) response = object_permissions_list_view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data[0].get('id'), 1) + self.assertEqual(response.data[0].get('id'), self.model.pk) def test_cannot_method_not_allowed(self): request = factory.generic('METHOD_NOT_ALLOWED', '/', HTTP_AUTHORIZATION=self.credentials['readonly']) @@ -506,36 +506,36 @@ class DeniedObjectViewWithDetail(PermissionInstanceView): class CustomPermissionsTests(TestCase): def setUp(self): - BasicModel(text='foo').save() + self.instance = BasicModel.objects.create(text='foo') User.objects.create_user('username', 'username@example.com', 'password') credentials = basic_auth_header('username', 'password') - self.request = factory.get('/1', format='json', HTTP_AUTHORIZATION=credentials) + self.request = factory.get('/%d' % self.instance.pk, format='json', HTTP_AUTHORIZATION=credentials) self.custom_message = 'Custom: You cannot access this resource' self.custom_code = 'permission_denied_custom' def test_permission_denied(self): - response = denied_view(self.request, pk=1) + response = denied_view(self.request, pk=self.instance.pk) detail = response.data.get('detail') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertNotEqual(detail, self.custom_message) self.assertNotEqual(detail.code, self.custom_code) def test_permission_denied_with_custom_detail(self): - response = denied_view_with_detail(self.request, pk=1) + response = denied_view_with_detail(self.request, pk=self.instance.pk) detail = response.data.get('detail') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(detail, self.custom_message) self.assertEqual(detail.code, self.custom_code) def test_permission_denied_for_object(self): - response = denied_object_view(self.request, pk=1) + response = denied_object_view(self.request, pk=self.instance.pk) detail = response.data.get('detail') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertNotEqual(detail, self.custom_message) self.assertNotEqual(detail.code, self.custom_code) def test_permission_denied_for_object_with_custom_detail(self): - response = denied_object_view_with_detail(self.request, pk=1) + response = denied_object_view_with_detail(self.request, pk=self.instance.pk) detail = response.data.get('detail') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(detail, self.custom_message) diff --git a/tests/test_prefetch_related.py b/tests/test_prefetch_related.py index 12ecbf2e6a..29b81249f1 100644 --- a/tests/test_prefetch_related.py +++ b/tests/test_prefetch_related.py @@ -34,7 +34,7 @@ def test_prefetch_related_updates(self): expected = { 'id': pk, 'username': 'new', - 'groups': [1], + 'groups': [groups_pk], 'email': 'tom@example.com' } assert response.data == expected @@ -52,7 +52,7 @@ def test_prefetch_related_excluding_instance_from_original_queryset(self): expected = { 'id': pk, 'username': 'exclude', - 'groups': [1], + 'groups': [groups_pk], 'email': 'tom@example.com' } assert response.data == expected diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py index f48bb98af8..1460396e76 100644 --- a/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -69,6 +69,40 @@ class Meta: fields = ('url', 'name', 'nullable_source') +# --- URL builder helpers --- + +def _url(prefix, model_path, pk): + return '%s/%s/%s/' % (prefix, model_path, pk) + + +def _m2m_source_url(pk, prefix='http://testserver'): + return _url(prefix, 'manytomanysource', pk) + + +def _m2m_target_url(pk, prefix='http://testserver'): + return _url(prefix, 'manytomanytarget', pk) + + +def _fk_source_url(pk, prefix='http://testserver'): + return _url(prefix, 'foreignkeysource', pk) + + +def _fk_target_url(pk, prefix='http://testserver'): + return _url(prefix, 'foreignkeytarget', pk) + + +def _nfk_source_url(pk, prefix='http://testserver'): + return _url(prefix, 'nullableforeignkeysource', pk) + + +def _o2o_target_url(pk, prefix='http://testserver'): + return _url(prefix, 'onetoonetarget', pk) + + +def _o2o_source_url(pk, prefix='http://testserver'): + return _url(prefix, 'nullableonetoonesource', pk) + + @override_settings(ROOT_URLCONF='tests.test_relations_hyperlink') class HyperlinkedManyToManyTests(TestCase): def setUp(self): @@ -80,24 +114,29 @@ def setUp(self): for target in ManyToManyTarget.objects.all(): source.targets.add(target) + self.targets = list(ManyToManyTarget.objects.order_by('pk')) + self.sources = list(ManyToManySource.objects.order_by('pk')) + self.t1, self.t2, self.t3 = self.targets + self.s1, self.s2, self.s3 = self.sources + def test_relative_hyperlinks(self): - queryset = ManyToManySource.objects.all() + queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True, context={'request': None}) expected = [ - {'url': '/manytomanysource/1/', 'name': 'source-1', 'targets': ['/manytomanytarget/1/']}, - {'url': '/manytomanysource/2/', 'name': 'source-2', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/']}, - {'url': '/manytomanysource/3/', 'name': 'source-3', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']} + {'url': _m2m_source_url(self.s1.pk, ''), 'name': 'source-1', 'targets': [_m2m_target_url(self.t1.pk, '')]}, + {'url': _m2m_source_url(self.s2.pk, ''), 'name': 'source-2', 'targets': [_m2m_target_url(self.t1.pk, ''), _m2m_target_url(self.t2.pk, '')]}, + {'url': _m2m_source_url(self.s3.pk, ''), 'name': 'source-3', 'targets': [_m2m_target_url(self.t1.pk, ''), _m2m_target_url(self.t2.pk, ''), _m2m_target_url(self.t3.pk, '')]} ] with self.assertNumQueries(4): assert serializer.data == expected def test_many_to_many_retrieve(self): - queryset = ManyToManySource.objects.all() + queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/']}, - {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, - {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} + {'url': _m2m_source_url(self.s1.pk), 'name': 'source-1', 'targets': [_m2m_target_url(self.t1.pk)]}, + {'url': _m2m_source_url(self.s2.pk), 'name': 'source-2', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk)]}, + {'url': _m2m_source_url(self.s3.pk), 'name': 'source-3', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk), _m2m_target_url(self.t3.pk)]} ] with self.assertNumQueries(4): assert serializer.data == expected @@ -109,94 +148,92 @@ def test_many_to_many_retrieve_prefetch_related(self): serializer.data def test_reverse_many_to_many_retrieve(self): - queryset = ManyToManyTarget.objects.all() + queryset = ManyToManyTarget.objects.order_by('pk') serializer = ManyToManyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/manytomanytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/manytomanysource/1/', 'http://testserver/manytomanysource/2/', 'http://testserver/manytomanysource/3/']}, - {'url': 'http://testserver/manytomanytarget/2/', 'name': 'target-2', 'sources': ['http://testserver/manytomanysource/2/', 'http://testserver/manytomanysource/3/']}, - {'url': 'http://testserver/manytomanytarget/3/', 'name': 'target-3', 'sources': ['http://testserver/manytomanysource/3/']} + {'url': _m2m_target_url(self.t1.pk), 'name': 'target-1', 'sources': [_m2m_source_url(self.s1.pk), _m2m_source_url(self.s2.pk), _m2m_source_url(self.s3.pk)]}, + {'url': _m2m_target_url(self.t2.pk), 'name': 'target-2', 'sources': [_m2m_source_url(self.s2.pk), _m2m_source_url(self.s3.pk)]}, + {'url': _m2m_target_url(self.t3.pk), 'name': 'target-3', 'sources': [_m2m_source_url(self.s3.pk)]} ] with self.assertNumQueries(4): assert serializer.data == expected def test_many_to_many_update(self): - data = {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} - instance = ManyToManySource.objects.get(pk=1) + data = {'url': _m2m_source_url(self.s1.pk), 'name': 'source-1', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk), _m2m_target_url(self.t3.pk)]} + instance = ManyToManySource.objects.get(pk=self.s1.pk) serializer = ManyToManySourceSerializer(instance, data=data, context={'request': request}) assert serializer.is_valid() serializer.save() assert serializer.data == data # Ensure source 1 is updated, and everything else is as expected - queryset = ManyToManySource.objects.all() + queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']}, - {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, - {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} + {'url': _m2m_source_url(self.s1.pk), 'name': 'source-1', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk), _m2m_target_url(self.t3.pk)]}, + {'url': _m2m_source_url(self.s2.pk), 'name': 'source-2', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk)]}, + {'url': _m2m_source_url(self.s3.pk), 'name': 'source-3', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk), _m2m_target_url(self.t3.pk)]} ] assert serializer.data == expected def test_reverse_many_to_many_update(self): - data = {'url': 'http://testserver/manytomanytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/manytomanysource/1/']} - instance = ManyToManyTarget.objects.get(pk=1) + data = {'url': _m2m_target_url(self.t1.pk), 'name': 'target-1', 'sources': [_m2m_source_url(self.s1.pk)]} + instance = ManyToManyTarget.objects.get(pk=self.t1.pk) serializer = ManyToManyTargetSerializer(instance, data=data, context={'request': request}) assert serializer.is_valid() serializer.save() assert serializer.data == data # Ensure target 1 is updated, and everything else is as expected - queryset = ManyToManyTarget.objects.all() + queryset = ManyToManyTarget.objects.order_by('pk') serializer = ManyToManyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/manytomanytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/manytomanysource/1/']}, - {'url': 'http://testserver/manytomanytarget/2/', 'name': 'target-2', 'sources': ['http://testserver/manytomanysource/2/', 'http://testserver/manytomanysource/3/']}, - {'url': 'http://testserver/manytomanytarget/3/', 'name': 'target-3', 'sources': ['http://testserver/manytomanysource/3/']} + {'url': _m2m_target_url(self.t1.pk), 'name': 'target-1', 'sources': [_m2m_source_url(self.s1.pk)]}, + {'url': _m2m_target_url(self.t2.pk), 'name': 'target-2', 'sources': [_m2m_source_url(self.s2.pk), _m2m_source_url(self.s3.pk)]}, + {'url': _m2m_target_url(self.t3.pk), 'name': 'target-3', 'sources': [_m2m_source_url(self.s3.pk)]} ] assert serializer.data == expected def test_many_to_many_create(self): - data = {'url': 'http://testserver/manytomanysource/4/', 'name': 'source-4', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/3/']} + data = {'url': 'http://testserver/manytomanysource/999/', 'name': 'source-4', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t3.pk)]} serializer = ManyToManySourceSerializer(data=data, context={'request': request}) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == data assert obj.name == 'source-4' # Ensure source 4 is added, and everything else is as expected - queryset = ManyToManySource.objects.all() + queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/']}, - {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, - {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']}, - {'url': 'http://testserver/manytomanysource/4/', 'name': 'source-4', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/3/']} + {'url': _m2m_source_url(self.s1.pk), 'name': 'source-1', 'targets': [_m2m_target_url(self.t1.pk)]}, + {'url': _m2m_source_url(self.s2.pk), 'name': 'source-2', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk)]}, + {'url': _m2m_source_url(self.s3.pk), 'name': 'source-3', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk), _m2m_target_url(self.t3.pk)]}, + {'url': _m2m_source_url(obj.pk), 'name': 'source-4', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t3.pk)]} ] assert serializer.data == expected def test_reverse_many_to_many_create(self): - data = {'url': 'http://testserver/manytomanytarget/4/', 'name': 'target-4', 'sources': ['http://testserver/manytomanysource/1/', 'http://testserver/manytomanysource/3/']} + data = {'url': 'http://testserver/manytomanytarget/999/', 'name': 'target-4', 'sources': [_m2m_source_url(self.s1.pk), _m2m_source_url(self.s3.pk)]} serializer = ManyToManyTargetSerializer(data=data, context={'request': request}) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == data assert obj.name == 'target-4' # Ensure target 4 is added, and everything else is as expected - queryset = ManyToManyTarget.objects.all() + queryset = ManyToManyTarget.objects.order_by('pk') serializer = ManyToManyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/manytomanytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/manytomanysource/1/', 'http://testserver/manytomanysource/2/', 'http://testserver/manytomanysource/3/']}, - {'url': 'http://testserver/manytomanytarget/2/', 'name': 'target-2', 'sources': ['http://testserver/manytomanysource/2/', 'http://testserver/manytomanysource/3/']}, - {'url': 'http://testserver/manytomanytarget/3/', 'name': 'target-3', 'sources': ['http://testserver/manytomanysource/3/']}, - {'url': 'http://testserver/manytomanytarget/4/', 'name': 'target-4', 'sources': ['http://testserver/manytomanysource/1/', 'http://testserver/manytomanysource/3/']} + {'url': _m2m_target_url(self.t1.pk), 'name': 'target-1', 'sources': [_m2m_source_url(self.s1.pk), _m2m_source_url(self.s2.pk), _m2m_source_url(self.s3.pk)]}, + {'url': _m2m_target_url(self.t2.pk), 'name': 'target-2', 'sources': [_m2m_source_url(self.s2.pk), _m2m_source_url(self.s3.pk)]}, + {'url': _m2m_target_url(self.t3.pk), 'name': 'target-3', 'sources': [_m2m_source_url(self.s3.pk)]}, + {'url': _m2m_target_url(obj.pk), 'name': 'target-4', 'sources': [_m2m_source_url(self.s1.pk), _m2m_source_url(self.s3.pk)]} ] assert serializer.data == expected def test_data_cannot_be_accessed_prior_to_is_valid(self): """Test that .data cannot be accessed prior to .is_valid for hyperlinked serializers.""" serializer = ManyToManySourceSerializer( - data={'name': 'test-source', 'targets': ['http://testserver/manytomanytarget/1/']}, + data={'name': 'test-source', 'targets': [_m2m_target_url(self.t1.pk)]}, context={'request': request} ) with pytest.raises(AssertionError): @@ -214,64 +251,69 @@ def setUp(self): source = ForeignKeySource(name='source-%d' % idx, target=target) source.save() + self.target1 = target + self.target2 = new_target + self.sources = list(ForeignKeySource.objects.order_by('pk')) + self.s1, self.s2, self.s3 = self.sources + def test_foreign_key_retrieve(self): - queryset = ForeignKeySource.objects.all() + queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/foreignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/foreignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/foreignkeysource/3/', 'name': 'source-3', 'target': 'http://testserver/foreignkeytarget/1/'} + {'url': _fk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target1.pk)}, + {'url': _fk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, + {'url': _fk_source_url(self.s3.pk), 'name': 'source-3', 'target': _fk_target_url(self.target1.pk)} ] with self.assertNumQueries(1): assert serializer.data == expected def test_reverse_foreign_key_retrieve(self): - queryset = ForeignKeyTarget.objects.all() + queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/foreignkeytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/foreignkeysource/1/', 'http://testserver/foreignkeysource/2/', 'http://testserver/foreignkeysource/3/']}, - {'url': 'http://testserver/foreignkeytarget/2/', 'name': 'target-2', 'sources': []}, + {'url': _fk_target_url(self.target1.pk), 'name': 'target-1', 'sources': [_fk_source_url(self.s1.pk), _fk_source_url(self.s2.pk), _fk_source_url(self.s3.pk)]}, + {'url': _fk_target_url(self.target2.pk), 'name': 'target-2', 'sources': []}, ] with self.assertNumQueries(3): assert serializer.data == expected def test_foreign_key_update(self): - data = {'url': 'http://testserver/foreignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/2/'} - instance = ForeignKeySource.objects.get(pk=1) + data = {'url': _fk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target2.pk)} + instance = ForeignKeySource.objects.get(pk=self.s1.pk) serializer = ForeignKeySourceSerializer(instance, data=data, context={'request': request}) assert serializer.is_valid() serializer.save() assert serializer.data == data # Ensure source 1 is updated, and everything else is as expected - queryset = ForeignKeySource.objects.all() + queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/foreignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/2/'}, - {'url': 'http://testserver/foreignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/foreignkeysource/3/', 'name': 'source-3', 'target': 'http://testserver/foreignkeytarget/1/'} + {'url': _fk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target2.pk)}, + {'url': _fk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, + {'url': _fk_source_url(self.s3.pk), 'name': 'source-3', 'target': _fk_target_url(self.target1.pk)} ] assert serializer.data == expected def test_foreign_key_update_incorrect_type(self): - data = {'url': 'http://testserver/foreignkeysource/1/', 'name': 'source-1', 'target': 2} - instance = ForeignKeySource.objects.get(pk=1) + data = {'url': _fk_source_url(self.s1.pk), 'name': 'source-1', 'target': 2} + instance = ForeignKeySource.objects.get(pk=self.s1.pk) serializer = ForeignKeySourceSerializer(instance, data=data, context={'request': request}) assert not serializer.is_valid() assert serializer.errors == {'target': ['Incorrect type. Expected URL string, received int.']} def test_reverse_foreign_key_update(self): - data = {'url': 'http://testserver/foreignkeytarget/2/', 'name': 'target-2', 'sources': ['http://testserver/foreignkeysource/1/', 'http://testserver/foreignkeysource/3/']} - instance = ForeignKeyTarget.objects.get(pk=2) + data = {'url': _fk_target_url(self.target2.pk), 'name': 'target-2', 'sources': [_fk_source_url(self.s1.pk), _fk_source_url(self.s3.pk)]} + instance = ForeignKeyTarget.objects.get(pk=self.target2.pk) serializer = ForeignKeyTargetSerializer(instance, data=data, context={'request': request}) assert serializer.is_valid() # We shouldn't have saved anything to the db yet since save # hasn't been called. - queryset = ForeignKeyTarget.objects.all() + queryset = ForeignKeyTarget.objects.order_by('pk') new_serializer = ForeignKeyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/foreignkeytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/foreignkeysource/1/', 'http://testserver/foreignkeysource/2/', 'http://testserver/foreignkeysource/3/']}, - {'url': 'http://testserver/foreignkeytarget/2/', 'name': 'target-2', 'sources': []}, + {'url': _fk_target_url(self.target1.pk), 'name': 'target-1', 'sources': [_fk_source_url(self.s1.pk), _fk_source_url(self.s2.pk), _fk_source_url(self.s3.pk)]}, + {'url': _fk_target_url(self.target2.pk), 'name': 'target-2', 'sources': []}, ] assert new_serializer.data == expected @@ -279,54 +321,52 @@ def test_reverse_foreign_key_update(self): assert serializer.data == data # Ensure target 2 is update, and everything else is as expected - queryset = ForeignKeyTarget.objects.all() + queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/foreignkeytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/foreignkeysource/2/']}, - {'url': 'http://testserver/foreignkeytarget/2/', 'name': 'target-2', 'sources': ['http://testserver/foreignkeysource/1/', 'http://testserver/foreignkeysource/3/']}, + {'url': _fk_target_url(self.target1.pk), 'name': 'target-1', 'sources': [_fk_source_url(self.s2.pk)]}, + {'url': _fk_target_url(self.target2.pk), 'name': 'target-2', 'sources': [_fk_source_url(self.s1.pk), _fk_source_url(self.s3.pk)]}, ] assert serializer.data == expected def test_foreign_key_create(self): - data = {'url': 'http://testserver/foreignkeysource/4/', 'name': 'source-4', 'target': 'http://testserver/foreignkeytarget/2/'} + data = {'url': 'http://testserver/foreignkeysource/999/', 'name': 'source-4', 'target': _fk_target_url(self.target2.pk)} serializer = ForeignKeySourceSerializer(data=data, context={'request': request}) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == data assert obj.name == 'source-4' # Ensure source 1 is updated, and everything else is as expected - queryset = ForeignKeySource.objects.all() + queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/foreignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/foreignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/foreignkeysource/3/', 'name': 'source-3', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/foreignkeysource/4/', 'name': 'source-4', 'target': 'http://testserver/foreignkeytarget/2/'}, + {'url': _fk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target1.pk)}, + {'url': _fk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, + {'url': _fk_source_url(self.s3.pk), 'name': 'source-3', 'target': _fk_target_url(self.target1.pk)}, + {'url': _fk_source_url(obj.pk), 'name': 'source-4', 'target': _fk_target_url(self.target2.pk)}, ] assert serializer.data == expected def test_reverse_foreign_key_create(self): - data = {'url': 'http://testserver/foreignkeytarget/3/', 'name': 'target-3', 'sources': ['http://testserver/foreignkeysource/1/', 'http://testserver/foreignkeysource/3/']} + data = {'url': 'http://testserver/foreignkeytarget/999/', 'name': 'target-3', 'sources': [_fk_source_url(self.s1.pk), _fk_source_url(self.s3.pk)]} serializer = ForeignKeyTargetSerializer(data=data, context={'request': request}) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == data assert obj.name == 'target-3' # Ensure target 4 is added, and everything else is as expected - queryset = ForeignKeyTarget.objects.all() + queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/foreignkeytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/foreignkeysource/2/']}, - {'url': 'http://testserver/foreignkeytarget/2/', 'name': 'target-2', 'sources': []}, - {'url': 'http://testserver/foreignkeytarget/3/', 'name': 'target-3', 'sources': ['http://testserver/foreignkeysource/1/', 'http://testserver/foreignkeysource/3/']}, + {'url': _fk_target_url(self.target1.pk), 'name': 'target-1', 'sources': [_fk_source_url(self.s2.pk)]}, + {'url': _fk_target_url(self.target2.pk), 'name': 'target-2', 'sources': []}, + {'url': _fk_target_url(obj.pk), 'name': 'target-3', 'sources': [_fk_source_url(self.s1.pk), _fk_source_url(self.s3.pk)]}, ] assert serializer.data == expected def test_foreign_key_update_with_invalid_null(self): - data = {'url': 'http://testserver/foreignkeysource/1/', 'name': 'source-1', 'target': None} - instance = ForeignKeySource.objects.get(pk=1) + data = {'url': _fk_source_url(self.s1.pk), 'name': 'source-1', 'target': None} + instance = ForeignKeySource.objects.get(pk=self.s1.pk) serializer = ForeignKeySourceSerializer(instance, data=data, context={'request': request}) assert not serializer.is_valid() assert serializer.errors == {'target': ['This field may not be null.']} @@ -343,32 +383,37 @@ def setUp(self): source = NullableForeignKeySource(name='source-%d' % idx, target=target) source.save() + self.target1 = ForeignKeyTarget.objects.get(name='target-1') + self.sources = list(NullableForeignKeySource.objects.order_by('pk')) + self.s1, self.s2, self.s3 = self.sources + def test_foreign_key_retrieve_with_null(self): - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/nullableforeignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None}, + {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target1.pk)}, + {'url': _nfk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, + {'url': _nfk_source_url(self.s3.pk), 'name': 'source-3', 'target': None}, ] assert serializer.data == expected def test_foreign_key_create_with_valid_null(self): - data = {'url': 'http://testserver/nullableforeignkeysource/4/', 'name': 'source-4', 'target': None} + data = {'url': 'http://testserver/nullableforeignkeysource/999/', 'name': 'source-4', 'target': None} serializer = NullableForeignKeySourceSerializer(data=data, context={'request': request}) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == data + expected_data = {'url': _nfk_source_url(obj.pk), 'name': 'source-4', 'target': None} + assert serializer.data == expected_data assert obj.name == 'source-4' # Ensure source 4 is created, and everything else is as expected - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/nullableforeignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None}, - {'url': 'http://testserver/nullableforeignkeysource/4/', 'name': 'source-4', 'target': None} + {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target1.pk)}, + {'url': _nfk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, + {'url': _nfk_source_url(self.s3.pk), 'name': 'source-3', 'target': None}, + {'url': _nfk_source_url(obj.pk), 'name': 'source-4', 'target': None} ] assert serializer.data == expected @@ -377,40 +422,40 @@ def test_foreign_key_create_with_valid_emptystring(self): The emptystring should be interpreted as null in the context of relationships. """ - data = {'url': 'http://testserver/nullableforeignkeysource/4/', 'name': 'source-4', 'target': ''} - expected_data = {'url': 'http://testserver/nullableforeignkeysource/4/', 'name': 'source-4', 'target': None} + data = {'url': 'http://testserver/nullableforeignkeysource/999/', 'name': 'source-4', 'target': ''} serializer = NullableForeignKeySourceSerializer(data=data, context={'request': request}) assert serializer.is_valid() obj = serializer.save() + expected_data = {'url': _nfk_source_url(obj.pk), 'name': 'source-4', 'target': None} assert serializer.data == expected_data assert obj.name == 'source-4' # Ensure source 4 is created, and everything else is as expected - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/nullableforeignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None}, - {'url': 'http://testserver/nullableforeignkeysource/4/', 'name': 'source-4', 'target': None} + {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target1.pk)}, + {'url': _nfk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, + {'url': _nfk_source_url(self.s3.pk), 'name': 'source-3', 'target': None}, + {'url': _nfk_source_url(obj.pk), 'name': 'source-4', 'target': None} ] assert serializer.data == expected def test_foreign_key_update_with_valid_null(self): - data = {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': None} - instance = NullableForeignKeySource.objects.get(pk=1) + data = {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=self.s1.pk) serializer = NullableForeignKeySourceSerializer(instance, data=data, context={'request': request}) assert serializer.is_valid() serializer.save() assert serializer.data == data # Ensure source 1 is updated, and everything else is as expected - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': None}, - {'url': 'http://testserver/nullableforeignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None}, + {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': None}, + {'url': _nfk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, + {'url': _nfk_source_url(self.s3.pk), 'name': 'source-3', 'target': None}, ] assert serializer.data == expected @@ -419,21 +464,21 @@ def test_foreign_key_update_with_valid_emptystring(self): The emptystring should be interpreted as null in the context of relationships. """ - data = {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': ''} - expected_data = {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': None} - instance = NullableForeignKeySource.objects.get(pk=1) + data = {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': ''} + expected_data = {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=self.s1.pk) serializer = NullableForeignKeySourceSerializer(instance, data=data, context={'request': request}) assert serializer.is_valid() serializer.save() assert serializer.data == expected_data # Ensure source 1 is updated, and everything else is as expected - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': None}, - {'url': 'http://testserver/nullableforeignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, - {'url': 'http://testserver/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None}, + {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': None}, + {'url': _nfk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, + {'url': _nfk_source_url(self.s3.pk), 'name': 'source-3', 'target': None}, ] assert serializer.data == expected @@ -448,11 +493,15 @@ def setUp(self): source = NullableOneToOneSource(name='source-1', target=target) source.save() + self.target1 = target + self.target2 = new_target + self.source1 = source + def test_reverse_foreign_key_retrieve_with_null(self): - queryset = OneToOneTarget.objects.all() + queryset = OneToOneTarget.objects.order_by('pk') serializer = NullableOneToOneTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/onetoonetarget/1/', 'name': 'target-1', 'nullable_source': 'http://testserver/nullableonetoonesource/1/'}, - {'url': 'http://testserver/onetoonetarget/2/', 'name': 'target-2', 'nullable_source': None}, + {'url': _o2o_target_url(self.target1.pk), 'name': 'target-1', 'nullable_source': _o2o_source_url(self.source1.pk)}, + {'url': _o2o_target_url(self.target2.pk), 'name': 'target-2', 'nullable_source': None}, ] assert serializer.data == expected diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index 3ca3726e24..0393844a00 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -97,94 +97,98 @@ class Meta: class PKManyToManyTests(TestCase): def setUp(self): + self.targets = [] + self.sources = [] for idx in range(1, 4): target = ManyToManyTarget(name='target-%d' % idx) target.save() + self.targets.append(target) source = ManyToManySource(name='source-%d' % idx) source.save() + self.sources.append(source) for target in ManyToManyTarget.objects.all(): source.targets.add(target) def test_many_to_many_retrieve(self): - queryset = ManyToManySource.objects.all() + queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'targets': [1]}, - {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, - {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]} + {'id': self.sources[0].pk, 'name': 'source-1', 'targets': [self.targets[0].pk]}, + {'id': self.sources[1].pk, 'name': 'source-2', 'targets': [self.targets[0].pk, self.targets[1].pk]}, + {'id': self.sources[2].pk, 'name': 'source-3', 'targets': [self.targets[0].pk, self.targets[1].pk, self.targets[2].pk]} ] with self.assertNumQueries(4): assert serializer.data == expected def test_many_to_many_retrieve_prefetch_related(self): - queryset = ManyToManySource.objects.all().prefetch_related('targets') + queryset = ManyToManySource.objects.order_by('pk').prefetch_related('targets') serializer = ManyToManySourceSerializer(queryset, many=True) with self.assertNumQueries(2): serializer.data def test_reverse_many_to_many_retrieve(self): - queryset = ManyToManyTarget.objects.all() + queryset = ManyToManyTarget.objects.order_by('pk') serializer = ManyToManyTargetSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]}, - {'id': 2, 'name': 'target-2', 'sources': [2, 3]}, - {'id': 3, 'name': 'target-3', 'sources': [3]} + {'id': self.targets[0].pk, 'name': 'target-1', 'sources': [self.sources[0].pk, self.sources[1].pk, self.sources[2].pk]}, + {'id': self.targets[1].pk, 'name': 'target-2', 'sources': [self.sources[1].pk, self.sources[2].pk]}, + {'id': self.targets[2].pk, 'name': 'target-3', 'sources': [self.sources[2].pk]} ] with self.assertNumQueries(4): assert serializer.data == expected def test_many_to_many_update(self): - data = {'id': 1, 'name': 'source-1', 'targets': [1, 2, 3]} - instance = ManyToManySource.objects.get(pk=1) + data = {'id': self.sources[0].pk, 'name': 'source-1', 'targets': [self.targets[0].pk, self.targets[1].pk, self.targets[2].pk]} + instance = ManyToManySource.objects.get(pk=self.sources[0].pk) serializer = ManyToManySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() assert serializer.data == data # Ensure source 1 is updated, and everything else is as expected - queryset = ManyToManySource.objects.all() + queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'targets': [1, 2, 3]}, - {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, - {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]} + {'id': self.sources[0].pk, 'name': 'source-1', 'targets': [self.targets[0].pk, self.targets[1].pk, self.targets[2].pk]}, + {'id': self.sources[1].pk, 'name': 'source-2', 'targets': [self.targets[0].pk, self.targets[1].pk]}, + {'id': self.sources[2].pk, 'name': 'source-3', 'targets': [self.targets[0].pk, self.targets[1].pk, self.targets[2].pk]} ] assert serializer.data == expected def test_reverse_many_to_many_update(self): - data = {'id': 1, 'name': 'target-1', 'sources': [1]} - instance = ManyToManyTarget.objects.get(pk=1) + data = {'id': self.targets[0].pk, 'name': 'target-1', 'sources': [self.sources[0].pk]} + instance = ManyToManyTarget.objects.get(pk=self.targets[0].pk) serializer = ManyToManyTargetSerializer(instance, data=data) assert serializer.is_valid() serializer.save() assert serializer.data == data # Ensure target 1 is updated, and everything else is as expected - queryset = ManyToManyTarget.objects.all() + queryset = ManyToManyTarget.objects.order_by('pk') serializer = ManyToManyTargetSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'target-1', 'sources': [1]}, - {'id': 2, 'name': 'target-2', 'sources': [2, 3]}, - {'id': 3, 'name': 'target-3', 'sources': [3]} + {'id': self.targets[0].pk, 'name': 'target-1', 'sources': [self.sources[0].pk]}, + {'id': self.targets[1].pk, 'name': 'target-2', 'sources': [self.sources[1].pk, self.sources[2].pk]}, + {'id': self.targets[2].pk, 'name': 'target-3', 'sources': [self.sources[2].pk]} ] assert serializer.data == expected def test_many_to_many_create(self): - data = {'id': 4, 'name': 'source-4', 'targets': [1, 3]} + data = {'name': 'source-4', 'targets': [self.targets[0].pk, self.targets[2].pk]} serializer = ManyToManySourceSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == data + assert serializer.data == {'id': obj.pk, 'name': 'source-4', 'targets': [self.targets[0].pk, self.targets[2].pk]} assert obj.name == 'source-4' # Ensure source 4 is added, and everything else is as expected - queryset = ManyToManySource.objects.all() + queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'targets': [1]}, - {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, - {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]}, - {'id': 4, 'name': 'source-4', 'targets': [1, 3]}, + {'id': self.sources[0].pk, 'name': 'source-1', 'targets': [self.targets[0].pk]}, + {'id': self.sources[1].pk, 'name': 'source-2', 'targets': [self.targets[0].pk, self.targets[1].pk]}, + {'id': self.sources[2].pk, 'name': 'source-3', 'targets': [self.targets[0].pk, self.targets[1].pk, self.targets[2].pk]}, + {'id': obj.pk, 'name': 'source-4', 'targets': [self.targets[0].pk, self.targets[2].pk]}, ] assert serializer.data == expected @@ -199,28 +203,28 @@ def test_many_to_many_unsaved(self): assert serializer.data == expected def test_reverse_many_to_many_create(self): - data = {'id': 4, 'name': 'target-4', 'sources': [1, 3]} + data = {'name': 'target-4', 'sources': [self.sources[0].pk, self.sources[2].pk]} serializer = ManyToManyTargetSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == data + assert serializer.data == {'id': obj.pk, 'name': 'target-4', 'sources': [self.sources[0].pk, self.sources[2].pk]} assert obj.name == 'target-4' # Ensure target 4 is added, and everything else is as expected - queryset = ManyToManyTarget.objects.all() + queryset = ManyToManyTarget.objects.order_by('pk') serializer = ManyToManyTargetSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]}, - {'id': 2, 'name': 'target-2', 'sources': [2, 3]}, - {'id': 3, 'name': 'target-3', 'sources': [3]}, - {'id': 4, 'name': 'target-4', 'sources': [1, 3]} + {'id': self.targets[0].pk, 'name': 'target-1', 'sources': [self.sources[0].pk, self.sources[1].pk, self.sources[2].pk]}, + {'id': self.targets[1].pk, 'name': 'target-2', 'sources': [self.sources[1].pk, self.sources[2].pk]}, + {'id': self.targets[2].pk, 'name': 'target-3', 'sources': [self.sources[2].pk]}, + {'id': obj.pk, 'name': 'target-4', 'sources': [self.sources[0].pk, self.sources[2].pk]} ] assert serializer.data == expected def test_data_cannot_be_accessed_prior_to_is_valid(self): """Test that .data cannot be accessed prior to .is_valid for primary key serializers.""" serializer = ManyToManySourceSerializer( - data={'name': 'test-source', 'targets': [1]} + data={'name': 'test-source', 'targets': [self.targets[0].pk]} ) with pytest.raises(AssertionError): serializer.data @@ -228,78 +232,80 @@ def test_data_cannot_be_accessed_prior_to_is_valid(self): class PKForeignKeyTests(TestCase): def setUp(self): - target = ForeignKeyTarget(name='target-1') - target.save() - new_target = ForeignKeyTarget(name='target-2') - new_target.save() + self.target = ForeignKeyTarget(name='target-1') + self.target.save() + self.new_target = ForeignKeyTarget(name='target-2') + self.new_target.save() + self.sources = [] for idx in range(1, 4): - source = ForeignKeySource(name='source-%d' % idx, target=target) + source = ForeignKeySource(name='source-%d' % idx, target=self.target) source.save() + self.sources.append(source) def test_foreign_key_retrieve(self): - queryset = ForeignKeySource.objects.all() + queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': 1}, - {'id': 2, 'name': 'source-2', 'target': 1}, - {'id': 3, 'name': 'source-3', 'target': 1} + {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.target.pk}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': self.target.pk} ] with self.assertNumQueries(1): assert serializer.data == expected def test_reverse_foreign_key_retrieve(self): - queryset = ForeignKeyTarget.objects.all() + queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]}, - {'id': 2, 'name': 'target-2', 'sources': []}, + {'id': self.target.pk, 'name': 'target-1', 'sources': [self.sources[0].pk, self.sources[1].pk, self.sources[2].pk]}, + {'id': self.new_target.pk, 'name': 'target-2', 'sources': []}, ] with self.assertNumQueries(3): assert serializer.data == expected def test_reverse_foreign_key_retrieve_prefetch_related(self): - queryset = ForeignKeyTarget.objects.all().prefetch_related('sources') + queryset = ForeignKeyTarget.objects.order_by('pk').prefetch_related('sources') serializer = ForeignKeyTargetSerializer(queryset, many=True) with self.assertNumQueries(2): serializer.data def test_foreign_key_update(self): - data = {'id': 1, 'name': 'source-1', 'target': 2} - instance = ForeignKeySource.objects.get(pk=1) + data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.new_target.pk} + instance = ForeignKeySource.objects.get(pk=self.sources[0].pk) serializer = ForeignKeySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() assert serializer.data == data # Ensure source 1 is updated, and everything else is as expected - queryset = ForeignKeySource.objects.all() + queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': 2}, - {'id': 2, 'name': 'source-2', 'target': 1}, - {'id': 3, 'name': 'source-3', 'target': 1} + {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.new_target.pk}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': self.target.pk} ] assert serializer.data == expected def test_foreign_key_update_incorrect_type(self): - data = {'id': 1, 'name': 'source-1', 'target': 'foo'} - instance = ForeignKeySource.objects.get(pk=1) + data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'foo'} + instance = ForeignKeySource.objects.get(pk=self.sources[0].pk) serializer = ForeignKeySourceSerializer(instance, data=data) assert not serializer.is_valid() assert serializer.errors == {'target': ['Incorrect type. Expected pk value, received str.']} def test_reverse_foreign_key_update(self): - data = {'id': 2, 'name': 'target-2', 'sources': [1, 3]} - instance = ForeignKeyTarget.objects.get(pk=2) + data = {'id': self.new_target.pk, 'name': 'target-2', 'sources': [self.sources[0].pk, self.sources[2].pk]} + instance = ForeignKeyTarget.objects.get(pk=self.new_target.pk) serializer = ForeignKeyTargetSerializer(instance, data=data) assert serializer.is_valid() # We shouldn't have saved anything to the db yet since save # hasn't been called. - queryset = ForeignKeyTarget.objects.all() + queryset = ForeignKeyTarget.objects.order_by('pk') new_serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]}, - {'id': 2, 'name': 'target-2', 'sources': []}, + {'id': self.target.pk, 'name': 'target-1', 'sources': [self.sources[0].pk, self.sources[1].pk, self.sources[2].pk]}, + {'id': self.new_target.pk, 'name': 'target-2', 'sources': []}, ] assert new_serializer.data == expected @@ -307,54 +313,54 @@ def test_reverse_foreign_key_update(self): assert serializer.data == data # Ensure target 2 is update, and everything else is as expected - queryset = ForeignKeyTarget.objects.all() + queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'target-1', 'sources': [2]}, - {'id': 2, 'name': 'target-2', 'sources': [1, 3]}, + {'id': self.target.pk, 'name': 'target-1', 'sources': [self.sources[1].pk]}, + {'id': self.new_target.pk, 'name': 'target-2', 'sources': [self.sources[0].pk, self.sources[2].pk]}, ] assert serializer.data == expected def test_foreign_key_create(self): - data = {'id': 4, 'name': 'source-4', 'target': 2} + data = {'name': 'source-4', 'target': self.new_target.pk} serializer = ForeignKeySourceSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == data + assert serializer.data == {'id': obj.pk, 'name': 'source-4', 'target': self.new_target.pk} assert obj.name == 'source-4' # Ensure source 4 is added, and everything else is as expected - queryset = ForeignKeySource.objects.all() + queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': 1}, - {'id': 2, 'name': 'source-2', 'target': 1}, - {'id': 3, 'name': 'source-3', 'target': 1}, - {'id': 4, 'name': 'source-4', 'target': 2}, + {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.target.pk}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': self.target.pk}, + {'id': obj.pk, 'name': 'source-4', 'target': self.new_target.pk}, ] assert serializer.data == expected def test_reverse_foreign_key_create(self): - data = {'id': 3, 'name': 'target-3', 'sources': [1, 3]} + data = {'name': 'target-3', 'sources': [self.sources[0].pk, self.sources[2].pk]} serializer = ForeignKeyTargetSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == data + assert serializer.data == {'id': obj.pk, 'name': 'target-3', 'sources': [self.sources[0].pk, self.sources[2].pk]} assert obj.name == 'target-3' # Ensure target 3 is added, and everything else is as expected - queryset = ForeignKeyTarget.objects.all() + queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'target-1', 'sources': [2]}, - {'id': 2, 'name': 'target-2', 'sources': []}, - {'id': 3, 'name': 'target-3', 'sources': [1, 3]}, + {'id': self.target.pk, 'name': 'target-1', 'sources': [self.sources[1].pk]}, + {'id': self.new_target.pk, 'name': 'target-2', 'sources': []}, + {'id': obj.pk, 'name': 'target-3', 'sources': [self.sources[0].pk, self.sources[2].pk]}, ] assert serializer.data == expected def test_foreign_key_update_with_invalid_null(self): - data = {'id': 1, 'name': 'source-1', 'target': None} - instance = ForeignKeySource.objects.get(pk=1) + data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': None} + instance = ForeignKeySource.objects.get(pk=self.sources[0].pk) serializer = ForeignKeySourceSerializer(instance, data=data) assert not serializer.is_valid() assert serializer.errors == {'target': ['This field may not be null.']} @@ -419,15 +425,15 @@ class PKRelationTests(TestCase): def setUp(self): self.target = ForeignKeyTarget.objects.create(name='target-1') - ForeignKeySource.objects.create(name='source-1', target=self.target) - ForeignKeySource.objects.create(name='source-2', target=self.target) + self.source1 = ForeignKeySource.objects.create(name='source-1', target=self.target) + self.source2 = ForeignKeySource.objects.create(name='source-2', target=self.target) def test_relation_field_callable_source(self): serializer = ForeignKeyTargetCallableSourceSerializer(self.target) expected = { - 'id': 1, + 'id': self.target.pk, 'name': 'target-1', - 'first_source': 1, + 'first_source': self.source1.pk, } with self.assertNumQueries(1): self.assertEqual(serializer.data, expected) @@ -435,9 +441,9 @@ def test_relation_field_callable_source(self): def test_relation_field_property_source(self): serializer = ForeignKeyTargetPropertySourceSerializer(self.target) expected = { - 'id': 1, + 'id': self.target.pk, 'name': 'target-1', - 'first_source': 1, + 'first_source': self.source1.pk, } with self.assertNumQueries(1): self.assertEqual(serializer.data, expected) @@ -445,40 +451,43 @@ def test_relation_field_property_source(self): class PKNullableForeignKeyTests(TestCase): def setUp(self): - target = ForeignKeyTarget(name='target-1') - target.save() + self.target = ForeignKeyTarget(name='target-1') + self.target.save() + self.sources = [] for idx in range(1, 4): + target = self.target if idx == 3: target = None source = NullableForeignKeySource(name='source-%d' % idx, target=target) source.save() + self.sources.append(source) def test_foreign_key_retrieve_with_null(self): - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': 1}, - {'id': 2, 'name': 'source-2', 'target': 1}, - {'id': 3, 'name': 'source-3', 'target': None}, + {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.target.pk}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': None}, ] assert serializer.data == expected def test_foreign_key_create_with_valid_null(self): - data = {'id': 4, 'name': 'source-4', 'target': None} + data = {'name': 'source-4', 'target': None} serializer = NullableForeignKeySourceSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == data + assert serializer.data == {'id': obj.pk, 'name': 'source-4', 'target': None} assert obj.name == 'source-4' # Ensure source 4 is created, and everything else is as expected - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': 1}, - {'id': 2, 'name': 'source-2', 'target': 1}, - {'id': 3, 'name': 'source-3', 'target': None}, - {'id': 4, 'name': 'source-4', 'target': None} + {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.target.pk}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': None}, + {'id': obj.pk, 'name': 'source-4', 'target': None} ] assert serializer.data == expected @@ -487,40 +496,40 @@ def test_foreign_key_create_with_valid_emptystring(self): The emptystring should be interpreted as null in the context of relationships. """ - data = {'id': 4, 'name': 'source-4', 'target': ''} - expected_data = {'id': 4, 'name': 'source-4', 'target': None} + data = {'name': 'source-4', 'target': ''} serializer = NullableForeignKeySourceSerializer(data=data) assert serializer.is_valid() obj = serializer.save() + expected_data = {'id': obj.pk, 'name': 'source-4', 'target': None} assert serializer.data == expected_data assert obj.name == 'source-4' # Ensure source 4 is created, and everything else is as expected - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': 1}, - {'id': 2, 'name': 'source-2', 'target': 1}, - {'id': 3, 'name': 'source-3', 'target': None}, - {'id': 4, 'name': 'source-4', 'target': None} + {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.target.pk}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': None}, + {'id': obj.pk, 'name': 'source-4', 'target': None} ] assert serializer.data == expected def test_foreign_key_update_with_valid_null(self): - data = {'id': 1, 'name': 'source-1', 'target': None} - instance = NullableForeignKeySource.objects.get(pk=1) + data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=self.sources[0].pk) serializer = NullableForeignKeySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() assert serializer.data == data # Ensure source 1 is updated, and everything else is as expected - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': None}, - {'id': 2, 'name': 'source-2', 'target': 1}, - {'id': 3, 'name': 'source-3', 'target': None} + {'id': self.sources[0].pk, 'name': 'source-1', 'target': None}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': None} ] assert serializer.data == expected @@ -529,21 +538,21 @@ def test_foreign_key_update_with_valid_emptystring(self): The emptystring should be interpreted as null in the context of relationships. """ - data = {'id': 1, 'name': 'source-1', 'target': ''} - expected_data = {'id': 1, 'name': 'source-1', 'target': None} - instance = NullableForeignKeySource.objects.get(pk=1) + data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': ''} + expected_data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=self.sources[0].pk) serializer = NullableForeignKeySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() assert serializer.data == expected_data # Ensure source 1 is updated, and everything else is as expected - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': None}, - {'id': 2, 'name': 'source-2', 'target': 1}, - {'id': 3, 'name': 'source-3', 'target': None} + {'id': self.sources[0].pk, 'name': 'source-1', 'target': None}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': None} ] assert serializer.data == expected @@ -561,19 +570,19 @@ def test_nullable_uuid_foreign_key_is_valid_when_none(self): class PKNullableOneToOneTests(TestCase): def setUp(self): - target = OneToOneTarget(name='target-1') - target.save() - new_target = OneToOneTarget(name='target-2') - new_target.save() - source = NullableOneToOneSource(name='source-1', target=new_target) - source.save() + self.target1 = OneToOneTarget(name='target-1') + self.target1.save() + self.target2 = OneToOneTarget(name='target-2') + self.target2.save() + self.source = NullableOneToOneSource(name='source-1', target=self.target2) + self.source.save() def test_reverse_foreign_key_retrieve_with_null(self): - queryset = OneToOneTarget.objects.all() + queryset = OneToOneTarget.objects.order_by('pk') serializer = NullableOneToOneTargetSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'target-1', 'nullable_source': None}, - {'id': 2, 'name': 'target-2', 'nullable_source': 1}, + {'id': self.target1.pk, 'name': 'target-1', 'nullable_source': None}, + {'id': self.target2.pk, 'name': 'target-2', 'nullable_source': self.source.pk}, ] assert serializer.data == expected diff --git a/tests/test_relations_slug.py b/tests/test_relations_slug.py index 0b9ca79d3d..b3ecc6e268 100644 --- a/tests/test_relations_slug.py +++ b/tests/test_relations_slug.py @@ -44,83 +44,85 @@ class Meta: # TODO: M2M Tests, FKTests (Non-nullable), One2One class SlugForeignKeyTests(TestCase): def setUp(self): - target = ForeignKeyTarget(name='target-1') - target.save() - new_target = ForeignKeyTarget(name='target-2') - new_target.save() + self.target = ForeignKeyTarget(name='target-1') + self.target.save() + self.new_target = ForeignKeyTarget(name='target-2') + self.new_target.save() + self.sources = [] for idx in range(1, 4): - source = ForeignKeySource(name='source-%d' % idx, target=target) + source = ForeignKeySource(name='source-%d' % idx, target=self.target) source.save() + self.sources.append(source) def test_foreign_key_retrieve(self): - queryset = ForeignKeySource.objects.all() + queryset = ForeignKeySource.objects.all().order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': 'target-1'}, - {'id': 2, 'name': 'source-2', 'target': 'target-1'}, - {'id': 3, 'name': 'source-3', 'target': 'target-1'} + {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-1'}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': 'target-1'} ] with self.assertNumQueries(4): assert serializer.data == expected def test_foreign_key_retrieve_select_related(self): - queryset = ForeignKeySource.objects.all().select_related('target') + queryset = ForeignKeySource.objects.all().order_by('pk').select_related('target') serializer = ForeignKeySourceSerializer(queryset, many=True) with self.assertNumQueries(1): serializer.data def test_reverse_foreign_key_retrieve(self): - queryset = ForeignKeyTarget.objects.all() + queryset = ForeignKeyTarget.objects.all().order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'target-1', 'sources': ['source-1', 'source-2', 'source-3']}, - {'id': 2, 'name': 'target-2', 'sources': []}, + {'id': self.target.pk, 'name': 'target-1', 'sources': ['source-1', 'source-2', 'source-3']}, + {'id': self.new_target.pk, 'name': 'target-2', 'sources': []}, ] assert serializer.data == expected def test_reverse_foreign_key_retrieve_prefetch_related(self): - queryset = ForeignKeyTarget.objects.all().prefetch_related('sources') + queryset = ForeignKeyTarget.objects.all().order_by('pk').prefetch_related('sources') serializer = ForeignKeyTargetSerializer(queryset, many=True) with self.assertNumQueries(2): serializer.data def test_foreign_key_update(self): - data = {'id': 1, 'name': 'source-1', 'target': 'target-2'} - instance = ForeignKeySource.objects.get(pk=1) + data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-2'} + instance = ForeignKeySource.objects.get(pk=self.sources[0].pk) serializer = ForeignKeySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() assert serializer.data == data # Ensure source 1 is updated, and everything else is as expected - queryset = ForeignKeySource.objects.all() + queryset = ForeignKeySource.objects.all().order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': 'target-2'}, - {'id': 2, 'name': 'source-2', 'target': 'target-1'}, - {'id': 3, 'name': 'source-3', 'target': 'target-1'} + {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-2'}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': 'target-1'} ] assert serializer.data == expected def test_foreign_key_update_incorrect_type(self): - data = {'id': 1, 'name': 'source-1', 'target': 123} - instance = ForeignKeySource.objects.get(pk=1) + data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': 123} + instance = ForeignKeySource.objects.get(pk=self.sources[0].pk) serializer = ForeignKeySourceSerializer(instance, data=data) assert not serializer.is_valid() assert serializer.errors == {'target': ['Object with name=123 does not exist.']} def test_reverse_foreign_key_update(self): - data = {'id': 2, 'name': 'target-2', 'sources': ['source-1', 'source-3']} - instance = ForeignKeyTarget.objects.get(pk=2) + data = {'id': self.new_target.pk, 'name': 'target-2', 'sources': ['source-1', 'source-3']} + instance = ForeignKeyTarget.objects.get(pk=self.new_target.pk) serializer = ForeignKeyTargetSerializer(instance, data=data) assert serializer.is_valid() # We shouldn't have saved anything to the db yet since save # hasn't been called. - queryset = ForeignKeyTarget.objects.all() + queryset = ForeignKeyTarget.objects.all().order_by('pk') new_serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'target-1', 'sources': ['source-1', 'source-2', 'source-3']}, - {'id': 2, 'name': 'target-2', 'sources': []}, + {'id': self.target.pk, 'name': 'target-1', 'sources': ['source-1', 'source-2', 'source-3']}, + {'id': self.new_target.pk, 'name': 'target-2', 'sources': []}, ] assert new_serializer.data == expected @@ -128,55 +130,57 @@ def test_reverse_foreign_key_update(self): assert serializer.data == data # Ensure target 2 is update, and everything else is as expected - queryset = ForeignKeyTarget.objects.all() + queryset = ForeignKeyTarget.objects.all().order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'target-1', 'sources': ['source-2']}, - {'id': 2, 'name': 'target-2', 'sources': ['source-1', 'source-3']}, + {'id': self.target.pk, 'name': 'target-1', 'sources': ['source-2']}, + {'id': self.new_target.pk, 'name': 'target-2', 'sources': ['source-1', 'source-3']}, ] assert serializer.data == expected def test_foreign_key_create(self): - data = {'id': 4, 'name': 'source-4', 'target': 'target-2'} + data = {'name': 'source-4', 'target': 'target-2'} serializer = ForeignKeySourceSerializer(data=data) serializer.is_valid() assert serializer.is_valid() obj = serializer.save() - assert serializer.data == data + expected_data = {'id': obj.pk, 'name': 'source-4', 'target': 'target-2'} + assert serializer.data == expected_data assert obj.name == 'source-4' # Ensure source 4 is added, and everything else is as expected - queryset = ForeignKeySource.objects.all() + queryset = ForeignKeySource.objects.all().order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': 'target-1'}, - {'id': 2, 'name': 'source-2', 'target': 'target-1'}, - {'id': 3, 'name': 'source-3', 'target': 'target-1'}, - {'id': 4, 'name': 'source-4', 'target': 'target-2'}, + {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-1'}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': 'target-1'}, + {'id': obj.pk, 'name': 'source-4', 'target': 'target-2'}, ] assert serializer.data == expected def test_reverse_foreign_key_create(self): - data = {'id': 3, 'name': 'target-3', 'sources': ['source-1', 'source-3']} + data = {'name': 'target-3', 'sources': ['source-1', 'source-3']} serializer = ForeignKeyTargetSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == data + expected_data = {'id': obj.pk, 'name': 'target-3', 'sources': ['source-1', 'source-3']} + assert serializer.data == expected_data assert obj.name == 'target-3' # Ensure target 3 is added, and everything else is as expected - queryset = ForeignKeyTarget.objects.all() + queryset = ForeignKeyTarget.objects.all().order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'target-1', 'sources': ['source-2']}, - {'id': 2, 'name': 'target-2', 'sources': []}, - {'id': 3, 'name': 'target-3', 'sources': ['source-1', 'source-3']}, + {'id': self.target.pk, 'name': 'target-1', 'sources': ['source-2']}, + {'id': self.new_target.pk, 'name': 'target-2', 'sources': []}, + {'id': obj.pk, 'name': 'target-3', 'sources': ['source-1', 'source-3']}, ] assert serializer.data == expected def test_foreign_key_update_with_invalid_null(self): - data = {'id': 1, 'name': 'source-1', 'target': None} - instance = ForeignKeySource.objects.get(pk=1) + data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': None} + instance = ForeignKeySource.objects.get(pk=self.sources[0].pk) serializer = ForeignKeySourceSerializer(instance, data=data) assert not serializer.is_valid() assert serializer.errors == {'target': ['This field may not be null.']} @@ -184,40 +188,44 @@ def test_foreign_key_update_with_invalid_null(self): class SlugNullableForeignKeyTests(TestCase): def setUp(self): - target = ForeignKeyTarget(name='target-1') - target.save() + self.target = ForeignKeyTarget(name='target-1') + self.target.save() + self.sources = [] for idx in range(1, 4): + target = self.target if idx == 3: target = None source = NullableForeignKeySource(name='source-%d' % idx, target=target) source.save() + self.sources.append(source) def test_foreign_key_retrieve_with_null(self): - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.all().order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': 'target-1'}, - {'id': 2, 'name': 'source-2', 'target': 'target-1'}, - {'id': 3, 'name': 'source-3', 'target': None}, + {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-1'}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': None}, ] assert serializer.data == expected def test_foreign_key_create_with_valid_null(self): - data = {'id': 4, 'name': 'source-4', 'target': None} + data = {'name': 'source-4', 'target': None} serializer = NullableForeignKeySourceSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == data + expected_data = {'id': obj.pk, 'name': 'source-4', 'target': None} + assert serializer.data == expected_data assert obj.name == 'source-4' # Ensure source 4 is created, and everything else is as expected - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.all().order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': 'target-1'}, - {'id': 2, 'name': 'source-2', 'target': 'target-1'}, - {'id': 3, 'name': 'source-3', 'target': None}, - {'id': 4, 'name': 'source-4', 'target': None} + {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-1'}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': None}, + {'id': obj.pk, 'name': 'source-4', 'target': None} ] assert serializer.data == expected @@ -226,40 +234,40 @@ def test_foreign_key_create_with_valid_emptystring(self): The emptystring should be interpreted as null in the context of relationships. """ - data = {'id': 4, 'name': 'source-4', 'target': ''} - expected_data = {'id': 4, 'name': 'source-4', 'target': None} + data = {'name': 'source-4', 'target': ''} serializer = NullableForeignKeySourceSerializer(data=data) assert serializer.is_valid() obj = serializer.save() + expected_data = {'id': obj.pk, 'name': 'source-4', 'target': None} assert serializer.data == expected_data assert obj.name == 'source-4' # Ensure source 4 is created, and everything else is as expected - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.all().order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': 'target-1'}, - {'id': 2, 'name': 'source-2', 'target': 'target-1'}, - {'id': 3, 'name': 'source-3', 'target': None}, - {'id': 4, 'name': 'source-4', 'target': None} + {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-1'}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': None}, + {'id': obj.pk, 'name': 'source-4', 'target': None} ] assert serializer.data == expected def test_foreign_key_update_with_valid_null(self): - data = {'id': 1, 'name': 'source-1', 'target': None} - instance = NullableForeignKeySource.objects.get(pk=1) + data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=self.sources[0].pk) serializer = NullableForeignKeySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() assert serializer.data == data # Ensure source 1 is updated, and everything else is as expected - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.all().order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': None}, - {'id': 2, 'name': 'source-2', 'target': 'target-1'}, - {'id': 3, 'name': 'source-3', 'target': None} + {'id': self.sources[0].pk, 'name': 'source-1', 'target': None}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': None} ] assert serializer.data == expected @@ -268,20 +276,20 @@ def test_foreign_key_update_with_valid_emptystring(self): The emptystring should be interpreted as null in the context of relationships. """ - data = {'id': 1, 'name': 'source-1', 'target': ''} - expected_data = {'id': 1, 'name': 'source-1', 'target': None} - instance = NullableForeignKeySource.objects.get(pk=1) + data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': ''} + expected_data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=self.sources[0].pk) serializer = NullableForeignKeySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() assert serializer.data == expected_data # Ensure source 1 is updated, and everything else is as expected - queryset = NullableForeignKeySource.objects.all() + queryset = NullableForeignKeySource.objects.all().order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'target': None}, - {'id': 2, 'name': 'source-2', 'target': 'target-1'}, - {'id': 3, 'name': 'source-3', 'target': None} + {'id': self.sources[0].pk, 'name': 'source-1', 'target': None}, + {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, + {'id': self.sources[2].pk, 'name': 'source-3', 'target': None} ] assert serializer.data == expected diff --git a/tests/test_validators.py b/tests/test_validators.py index 96354b9b13..cf1bfd09bc 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -662,13 +662,13 @@ def setUp(self): global_id=1, fancy_conditions=1 ) - UniqueConstraintModel.objects.create( + self.instance2 = UniqueConstraintModel.objects.create( race_name='example', position=2, global_id=2, fancy_conditions=1 ) - UniqueConstraintModel.objects.create( + self.instance3 = UniqueConstraintModel.objects.create( race_name='other', position=1, global_id=3, @@ -753,7 +753,7 @@ def test_single_field_uniq_validators(self): validators = serializer.fields['fancy_conditions'].validators assert len(validators) == 2 + extra_validators_qty ids_in_qs = {frozenset(v.queryset.values_list('id', flat=True)) for v in validators if hasattr(v, "queryset")} - assert ids_in_qs == {frozenset([1]), frozenset([3])} + assert ids_in_qs == {frozenset([self.instance.pk]), frozenset([self.instance3.pk])} def test_nullable_unique_constraint_fields_are_not_required(self): serializer = UniqueConstraintNullableSerializer(data={'title': 'Bob'}) From bb7559b3984b483e184e64e772610fa83d9dc4b9 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 7 Apr 2026 16:59:56 -0300 Subject: [PATCH 05/12] Fix validator tests to handle database-specific integer range validators --- tests/test_validators.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_validators.py b/tests/test_validators.py index cf1bfd09bc..aaef20f4ce 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -4,7 +4,7 @@ import pytest from django import VERSION as django_version -from django.db import DataError, models +from django.db import DataError, connection, models from django.test import TestCase from rest_framework import serializers @@ -742,8 +742,11 @@ def test_single_field_uniq_validators(self): UniqueConstraint with single field must be transformed into field's UniqueValidator """ - # Django 5 includes Max and Min values validators for IntegerField - extra_validators_qty = 2 if django_version[0] >= 5 else 0 + # Backends like PostgreSQL add Min/Max validators for IntegerField; + # SQLite does not because it has no fixed integer range. + has_int_range = connection.ops.integer_field_range('IntegerField')[0] is not None + extra_validators_qty = 2 if has_int_range else 0 + serializer = UniqueConstraintSerializer() assert len(serializer.validators) == 2 validators = serializer.fields['global_id'].validators From 2ce3d4ddb0aa86609651071f0c9df3255d8b09fe Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 7 Apr 2026 17:44:35 -0300 Subject: [PATCH 06/12] Don't pass DATABASE_URL in base env --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 63e075c1c1..0bb3c6aa1d 100644 --- a/tox.ini +++ b/tox.ini @@ -32,6 +32,7 @@ dependency_groups = dependency_groups = test deps = +pass_env = [testenv:dist] commands = python -W error::DeprecationWarning -W error::PendingDeprecationWarning runtests.py --no-pkgroot --staticfiles {posargs} From 6b361dfe00873aa1a50277a9f5a554dbabe66386 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 7 Apr 2026 18:24:01 -0300 Subject: [PATCH 07/12] Reset database sequences in hyperlink relation tests to ensure consistent PKs instead of complex URL builders --- tests/test_relations_hyperlink.py | 246 +++++++++++++----------------- 1 file changed, 109 insertions(+), 137 deletions(-) diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py index 1460396e76..aa1c930a31 100644 --- a/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -1,4 +1,6 @@ import pytest +from django.core.management.color import no_style +from django.db import connection from django.test import TestCase, override_settings from django.urls import path @@ -69,43 +71,25 @@ class Meta: fields = ('url', 'name', 'nullable_source') -# --- URL builder helpers --- +def _reset_sequences(*models): + """Reset database sequences for the given models so PKs start from 1. -def _url(prefix, model_path, pk): - return '%s/%s/%s/' % (prefix, model_path, pk) - - -def _m2m_source_url(pk, prefix='http://testserver'): - return _url(prefix, 'manytomanysource', pk) - - -def _m2m_target_url(pk, prefix='http://testserver'): - return _url(prefix, 'manytomanytarget', pk) - - -def _fk_source_url(pk, prefix='http://testserver'): - return _url(prefix, 'foreignkeysource', pk) - - -def _fk_target_url(pk, prefix='http://testserver'): - return _url(prefix, 'foreignkeytarget', pk) - - -def _nfk_source_url(pk, prefix='http://testserver'): - return _url(prefix, 'nullableforeignkeysource', pk) - - -def _o2o_target_url(pk, prefix='http://testserver'): - return _url(prefix, 'onetoonetarget', pk) - - -def _o2o_source_url(pk, prefix='http://testserver'): - return _url(prefix, 'nullableonetoonesource', pk) + PostgreSQL sequence operations are non-transactional, so this works + even inside TestCase's transaction wrapper. No-op on SQLite. + """ + if connection.vendor != 'postgresql': + return + sql_list = connection.ops.sequence_reset_sql(no_style(), models) + if sql_list: + with connection.cursor() as cursor: + for sql in sql_list: + cursor.execute(sql) @override_settings(ROOT_URLCONF='tests.test_relations_hyperlink') class HyperlinkedManyToManyTests(TestCase): def setUp(self): + _reset_sequences(ManyToManySource, ManyToManyTarget) for idx in range(1, 4): target = ManyToManyTarget(name='target-%d' % idx) target.save() @@ -114,18 +98,13 @@ def setUp(self): for target in ManyToManyTarget.objects.all(): source.targets.add(target) - self.targets = list(ManyToManyTarget.objects.order_by('pk')) - self.sources = list(ManyToManySource.objects.order_by('pk')) - self.t1, self.t2, self.t3 = self.targets - self.s1, self.s2, self.s3 = self.sources - def test_relative_hyperlinks(self): queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True, context={'request': None}) expected = [ - {'url': _m2m_source_url(self.s1.pk, ''), 'name': 'source-1', 'targets': [_m2m_target_url(self.t1.pk, '')]}, - {'url': _m2m_source_url(self.s2.pk, ''), 'name': 'source-2', 'targets': [_m2m_target_url(self.t1.pk, ''), _m2m_target_url(self.t2.pk, '')]}, - {'url': _m2m_source_url(self.s3.pk, ''), 'name': 'source-3', 'targets': [_m2m_target_url(self.t1.pk, ''), _m2m_target_url(self.t2.pk, ''), _m2m_target_url(self.t3.pk, '')]} + {'url': '/manytomanysource/1/', 'name': 'source-1', 'targets': ['/manytomanytarget/1/']}, + {'url': '/manytomanysource/2/', 'name': 'source-2', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/']}, + {'url': '/manytomanysource/3/', 'name': 'source-3', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']} ] with self.assertNumQueries(4): assert serializer.data == expected @@ -134,9 +113,9 @@ def test_many_to_many_retrieve(self): queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _m2m_source_url(self.s1.pk), 'name': 'source-1', 'targets': [_m2m_target_url(self.t1.pk)]}, - {'url': _m2m_source_url(self.s2.pk), 'name': 'source-2', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk)]}, - {'url': _m2m_source_url(self.s3.pk), 'name': 'source-3', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk), _m2m_target_url(self.t3.pk)]} + {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/']}, + {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, + {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} ] with self.assertNumQueries(4): assert serializer.data == expected @@ -151,16 +130,16 @@ def test_reverse_many_to_many_retrieve(self): queryset = ManyToManyTarget.objects.order_by('pk') serializer = ManyToManyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _m2m_target_url(self.t1.pk), 'name': 'target-1', 'sources': [_m2m_source_url(self.s1.pk), _m2m_source_url(self.s2.pk), _m2m_source_url(self.s3.pk)]}, - {'url': _m2m_target_url(self.t2.pk), 'name': 'target-2', 'sources': [_m2m_source_url(self.s2.pk), _m2m_source_url(self.s3.pk)]}, - {'url': _m2m_target_url(self.t3.pk), 'name': 'target-3', 'sources': [_m2m_source_url(self.s3.pk)]} + {'url': 'http://testserver/manytomanytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/manytomanysource/1/', 'http://testserver/manytomanysource/2/', 'http://testserver/manytomanysource/3/']}, + {'url': 'http://testserver/manytomanytarget/2/', 'name': 'target-2', 'sources': ['http://testserver/manytomanysource/2/', 'http://testserver/manytomanysource/3/']}, + {'url': 'http://testserver/manytomanytarget/3/', 'name': 'target-3', 'sources': ['http://testserver/manytomanysource/3/']} ] with self.assertNumQueries(4): assert serializer.data == expected def test_many_to_many_update(self): - data = {'url': _m2m_source_url(self.s1.pk), 'name': 'source-1', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk), _m2m_target_url(self.t3.pk)]} - instance = ManyToManySource.objects.get(pk=self.s1.pk) + data = {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} + instance = ManyToManySource.objects.get(pk=1) serializer = ManyToManySourceSerializer(instance, data=data, context={'request': request}) assert serializer.is_valid() serializer.save() @@ -170,15 +149,15 @@ def test_many_to_many_update(self): queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _m2m_source_url(self.s1.pk), 'name': 'source-1', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk), _m2m_target_url(self.t3.pk)]}, - {'url': _m2m_source_url(self.s2.pk), 'name': 'source-2', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk)]}, - {'url': _m2m_source_url(self.s3.pk), 'name': 'source-3', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk), _m2m_target_url(self.t3.pk)]} + {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']}, + {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, + {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} ] assert serializer.data == expected def test_reverse_many_to_many_update(self): - data = {'url': _m2m_target_url(self.t1.pk), 'name': 'target-1', 'sources': [_m2m_source_url(self.s1.pk)]} - instance = ManyToManyTarget.objects.get(pk=self.t1.pk) + data = {'url': 'http://testserver/manytomanytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/manytomanysource/1/']} + instance = ManyToManyTarget.objects.get(pk=1) serializer = ManyToManyTargetSerializer(instance, data=data, context={'request': request}) assert serializer.is_valid() serializer.save() @@ -187,53 +166,55 @@ def test_reverse_many_to_many_update(self): queryset = ManyToManyTarget.objects.order_by('pk') serializer = ManyToManyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _m2m_target_url(self.t1.pk), 'name': 'target-1', 'sources': [_m2m_source_url(self.s1.pk)]}, - {'url': _m2m_target_url(self.t2.pk), 'name': 'target-2', 'sources': [_m2m_source_url(self.s2.pk), _m2m_source_url(self.s3.pk)]}, - {'url': _m2m_target_url(self.t3.pk), 'name': 'target-3', 'sources': [_m2m_source_url(self.s3.pk)]} + {'url': 'http://testserver/manytomanytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/manytomanysource/1/']}, + {'url': 'http://testserver/manytomanytarget/2/', 'name': 'target-2', 'sources': ['http://testserver/manytomanysource/2/', 'http://testserver/manytomanysource/3/']}, + {'url': 'http://testserver/manytomanytarget/3/', 'name': 'target-3', 'sources': ['http://testserver/manytomanysource/3/']} ] assert serializer.data == expected def test_many_to_many_create(self): - data = {'url': 'http://testserver/manytomanysource/999/', 'name': 'source-4', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t3.pk)]} + data = {'url': 'http://testserver/manytomanysource/4/', 'name': 'source-4', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/3/']} serializer = ManyToManySourceSerializer(data=data, context={'request': request}) assert serializer.is_valid() obj = serializer.save() + assert serializer.data == data assert obj.name == 'source-4' # Ensure source 4 is added, and everything else is as expected queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _m2m_source_url(self.s1.pk), 'name': 'source-1', 'targets': [_m2m_target_url(self.t1.pk)]}, - {'url': _m2m_source_url(self.s2.pk), 'name': 'source-2', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk)]}, - {'url': _m2m_source_url(self.s3.pk), 'name': 'source-3', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t2.pk), _m2m_target_url(self.t3.pk)]}, - {'url': _m2m_source_url(obj.pk), 'name': 'source-4', 'targets': [_m2m_target_url(self.t1.pk), _m2m_target_url(self.t3.pk)]} + {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/']}, + {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, + {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']}, + {'url': 'http://testserver/manytomanysource/4/', 'name': 'source-4', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/3/']} ] assert serializer.data == expected def test_reverse_many_to_many_create(self): - data = {'url': 'http://testserver/manytomanytarget/999/', 'name': 'target-4', 'sources': [_m2m_source_url(self.s1.pk), _m2m_source_url(self.s3.pk)]} + data = {'url': 'http://testserver/manytomanytarget/4/', 'name': 'target-4', 'sources': ['http://testserver/manytomanysource/1/', 'http://testserver/manytomanysource/3/']} serializer = ManyToManyTargetSerializer(data=data, context={'request': request}) assert serializer.is_valid() obj = serializer.save() + assert serializer.data == data assert obj.name == 'target-4' # Ensure target 4 is added, and everything else is as expected queryset = ManyToManyTarget.objects.order_by('pk') serializer = ManyToManyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _m2m_target_url(self.t1.pk), 'name': 'target-1', 'sources': [_m2m_source_url(self.s1.pk), _m2m_source_url(self.s2.pk), _m2m_source_url(self.s3.pk)]}, - {'url': _m2m_target_url(self.t2.pk), 'name': 'target-2', 'sources': [_m2m_source_url(self.s2.pk), _m2m_source_url(self.s3.pk)]}, - {'url': _m2m_target_url(self.t3.pk), 'name': 'target-3', 'sources': [_m2m_source_url(self.s3.pk)]}, - {'url': _m2m_target_url(obj.pk), 'name': 'target-4', 'sources': [_m2m_source_url(self.s1.pk), _m2m_source_url(self.s3.pk)]} + {'url': 'http://testserver/manytomanytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/manytomanysource/1/', 'http://testserver/manytomanysource/2/', 'http://testserver/manytomanysource/3/']}, + {'url': 'http://testserver/manytomanytarget/2/', 'name': 'target-2', 'sources': ['http://testserver/manytomanysource/2/', 'http://testserver/manytomanysource/3/']}, + {'url': 'http://testserver/manytomanytarget/3/', 'name': 'target-3', 'sources': ['http://testserver/manytomanysource/3/']}, + {'url': 'http://testserver/manytomanytarget/4/', 'name': 'target-4', 'sources': ['http://testserver/manytomanysource/1/', 'http://testserver/manytomanysource/3/']} ] assert serializer.data == expected def test_data_cannot_be_accessed_prior_to_is_valid(self): """Test that .data cannot be accessed prior to .is_valid for hyperlinked serializers.""" serializer = ManyToManySourceSerializer( - data={'name': 'test-source', 'targets': [_m2m_target_url(self.t1.pk)]}, + data={'name': 'test-source', 'targets': ['http://testserver/manytomanytarget/1/']}, context={'request': request} ) with pytest.raises(AssertionError): @@ -243,6 +224,7 @@ def test_data_cannot_be_accessed_prior_to_is_valid(self): @override_settings(ROOT_URLCONF='tests.test_relations_hyperlink') class HyperlinkedForeignKeyTests(TestCase): def setUp(self): + _reset_sequences(ForeignKeySource, ForeignKeyTarget) target = ForeignKeyTarget(name='target-1') target.save() new_target = ForeignKeyTarget(name='target-2') @@ -251,18 +233,13 @@ def setUp(self): source = ForeignKeySource(name='source-%d' % idx, target=target) source.save() - self.target1 = target - self.target2 = new_target - self.sources = list(ForeignKeySource.objects.order_by('pk')) - self.s1, self.s2, self.s3 = self.sources - def test_foreign_key_retrieve(self): queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _fk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target1.pk)}, - {'url': _fk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, - {'url': _fk_source_url(self.s3.pk), 'name': 'source-3', 'target': _fk_target_url(self.target1.pk)} + {'url': 'http://testserver/foreignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/foreignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/foreignkeysource/3/', 'name': 'source-3', 'target': 'http://testserver/foreignkeytarget/1/'} ] with self.assertNumQueries(1): assert serializer.data == expected @@ -271,15 +248,15 @@ def test_reverse_foreign_key_retrieve(self): queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _fk_target_url(self.target1.pk), 'name': 'target-1', 'sources': [_fk_source_url(self.s1.pk), _fk_source_url(self.s2.pk), _fk_source_url(self.s3.pk)]}, - {'url': _fk_target_url(self.target2.pk), 'name': 'target-2', 'sources': []}, + {'url': 'http://testserver/foreignkeytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/foreignkeysource/1/', 'http://testserver/foreignkeysource/2/', 'http://testserver/foreignkeysource/3/']}, + {'url': 'http://testserver/foreignkeytarget/2/', 'name': 'target-2', 'sources': []}, ] with self.assertNumQueries(3): assert serializer.data == expected def test_foreign_key_update(self): - data = {'url': _fk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target2.pk)} - instance = ForeignKeySource.objects.get(pk=self.s1.pk) + data = {'url': 'http://testserver/foreignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/2/'} + instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data, context={'request': request}) assert serializer.is_valid() serializer.save() @@ -289,22 +266,22 @@ def test_foreign_key_update(self): queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _fk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target2.pk)}, - {'url': _fk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, - {'url': _fk_source_url(self.s3.pk), 'name': 'source-3', 'target': _fk_target_url(self.target1.pk)} + {'url': 'http://testserver/foreignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/2/'}, + {'url': 'http://testserver/foreignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/foreignkeysource/3/', 'name': 'source-3', 'target': 'http://testserver/foreignkeytarget/1/'} ] assert serializer.data == expected def test_foreign_key_update_incorrect_type(self): - data = {'url': _fk_source_url(self.s1.pk), 'name': 'source-1', 'target': 2} - instance = ForeignKeySource.objects.get(pk=self.s1.pk) + data = {'url': 'http://testserver/foreignkeysource/1/', 'name': 'source-1', 'target': 2} + instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data, context={'request': request}) assert not serializer.is_valid() assert serializer.errors == {'target': ['Incorrect type. Expected URL string, received int.']} def test_reverse_foreign_key_update(self): - data = {'url': _fk_target_url(self.target2.pk), 'name': 'target-2', 'sources': [_fk_source_url(self.s1.pk), _fk_source_url(self.s3.pk)]} - instance = ForeignKeyTarget.objects.get(pk=self.target2.pk) + data = {'url': 'http://testserver/foreignkeytarget/2/', 'name': 'target-2', 'sources': ['http://testserver/foreignkeysource/1/', 'http://testserver/foreignkeysource/3/']} + instance = ForeignKeyTarget.objects.get(pk=2) serializer = ForeignKeyTargetSerializer(instance, data=data, context={'request': request}) assert serializer.is_valid() # We shouldn't have saved anything to the db yet since save @@ -312,8 +289,8 @@ def test_reverse_foreign_key_update(self): queryset = ForeignKeyTarget.objects.order_by('pk') new_serializer = ForeignKeyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _fk_target_url(self.target1.pk), 'name': 'target-1', 'sources': [_fk_source_url(self.s1.pk), _fk_source_url(self.s2.pk), _fk_source_url(self.s3.pk)]}, - {'url': _fk_target_url(self.target2.pk), 'name': 'target-2', 'sources': []}, + {'url': 'http://testserver/foreignkeytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/foreignkeysource/1/', 'http://testserver/foreignkeysource/2/', 'http://testserver/foreignkeysource/3/']}, + {'url': 'http://testserver/foreignkeytarget/2/', 'name': 'target-2', 'sources': []}, ] assert new_serializer.data == expected @@ -324,49 +301,51 @@ def test_reverse_foreign_key_update(self): queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _fk_target_url(self.target1.pk), 'name': 'target-1', 'sources': [_fk_source_url(self.s2.pk)]}, - {'url': _fk_target_url(self.target2.pk), 'name': 'target-2', 'sources': [_fk_source_url(self.s1.pk), _fk_source_url(self.s3.pk)]}, + {'url': 'http://testserver/foreignkeytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/foreignkeysource/2/']}, + {'url': 'http://testserver/foreignkeytarget/2/', 'name': 'target-2', 'sources': ['http://testserver/foreignkeysource/1/', 'http://testserver/foreignkeysource/3/']}, ] assert serializer.data == expected def test_foreign_key_create(self): - data = {'url': 'http://testserver/foreignkeysource/999/', 'name': 'source-4', 'target': _fk_target_url(self.target2.pk)} + data = {'url': 'http://testserver/foreignkeysource/4/', 'name': 'source-4', 'target': 'http://testserver/foreignkeytarget/2/'} serializer = ForeignKeySourceSerializer(data=data, context={'request': request}) assert serializer.is_valid() obj = serializer.save() + assert serializer.data == data assert obj.name == 'source-4' # Ensure source 1 is updated, and everything else is as expected queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _fk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target1.pk)}, - {'url': _fk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, - {'url': _fk_source_url(self.s3.pk), 'name': 'source-3', 'target': _fk_target_url(self.target1.pk)}, - {'url': _fk_source_url(obj.pk), 'name': 'source-4', 'target': _fk_target_url(self.target2.pk)}, + {'url': 'http://testserver/foreignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/foreignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/foreignkeysource/3/', 'name': 'source-3', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/foreignkeysource/4/', 'name': 'source-4', 'target': 'http://testserver/foreignkeytarget/2/'}, ] assert serializer.data == expected def test_reverse_foreign_key_create(self): - data = {'url': 'http://testserver/foreignkeytarget/999/', 'name': 'target-3', 'sources': [_fk_source_url(self.s1.pk), _fk_source_url(self.s3.pk)]} + data = {'url': 'http://testserver/foreignkeytarget/3/', 'name': 'target-3', 'sources': ['http://testserver/foreignkeysource/1/', 'http://testserver/foreignkeysource/3/']} serializer = ForeignKeyTargetSerializer(data=data, context={'request': request}) assert serializer.is_valid() obj = serializer.save() + assert serializer.data == data assert obj.name == 'target-3' # Ensure target 4 is added, and everything else is as expected queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _fk_target_url(self.target1.pk), 'name': 'target-1', 'sources': [_fk_source_url(self.s2.pk)]}, - {'url': _fk_target_url(self.target2.pk), 'name': 'target-2', 'sources': []}, - {'url': _fk_target_url(obj.pk), 'name': 'target-3', 'sources': [_fk_source_url(self.s1.pk), _fk_source_url(self.s3.pk)]}, + {'url': 'http://testserver/foreignkeytarget/1/', 'name': 'target-1', 'sources': ['http://testserver/foreignkeysource/2/']}, + {'url': 'http://testserver/foreignkeytarget/2/', 'name': 'target-2', 'sources': []}, + {'url': 'http://testserver/foreignkeytarget/3/', 'name': 'target-3', 'sources': ['http://testserver/foreignkeysource/1/', 'http://testserver/foreignkeysource/3/']}, ] assert serializer.data == expected def test_foreign_key_update_with_invalid_null(self): - data = {'url': _fk_source_url(self.s1.pk), 'name': 'source-1', 'target': None} - instance = ForeignKeySource.objects.get(pk=self.s1.pk) + data = {'url': 'http://testserver/foreignkeysource/1/', 'name': 'source-1', 'target': None} + instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data, context={'request': request}) assert not serializer.is_valid() assert serializer.errors == {'target': ['This field may not be null.']} @@ -375,6 +354,7 @@ def test_foreign_key_update_with_invalid_null(self): @override_settings(ROOT_URLCONF='tests.test_relations_hyperlink') class HyperlinkedNullableForeignKeyTests(TestCase): def setUp(self): + _reset_sequences(NullableForeignKeySource, ForeignKeyTarget) target = ForeignKeyTarget(name='target-1') target.save() for idx in range(1, 4): @@ -383,37 +363,32 @@ def setUp(self): source = NullableForeignKeySource(name='source-%d' % idx, target=target) source.save() - self.target1 = ForeignKeyTarget.objects.get(name='target-1') - self.sources = list(NullableForeignKeySource.objects.order_by('pk')) - self.s1, self.s2, self.s3 = self.sources - def test_foreign_key_retrieve_with_null(self): queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target1.pk)}, - {'url': _nfk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, - {'url': _nfk_source_url(self.s3.pk), 'name': 'source-3', 'target': None}, + {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/nullableforeignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None}, ] assert serializer.data == expected def test_foreign_key_create_with_valid_null(self): - data = {'url': 'http://testserver/nullableforeignkeysource/999/', 'name': 'source-4', 'target': None} + data = {'url': 'http://testserver/nullableforeignkeysource/4/', 'name': 'source-4', 'target': None} serializer = NullableForeignKeySourceSerializer(data=data, context={'request': request}) assert serializer.is_valid() obj = serializer.save() - expected_data = {'url': _nfk_source_url(obj.pk), 'name': 'source-4', 'target': None} - assert serializer.data == expected_data + assert serializer.data == data assert obj.name == 'source-4' # Ensure source 4 is created, and everything else is as expected queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target1.pk)}, - {'url': _nfk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, - {'url': _nfk_source_url(self.s3.pk), 'name': 'source-3', 'target': None}, - {'url': _nfk_source_url(obj.pk), 'name': 'source-4', 'target': None} + {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/nullableforeignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None}, + {'url': 'http://testserver/nullableforeignkeysource/4/', 'name': 'source-4', 'target': None} ] assert serializer.data == expected @@ -422,11 +397,11 @@ def test_foreign_key_create_with_valid_emptystring(self): The emptystring should be interpreted as null in the context of relationships. """ - data = {'url': 'http://testserver/nullableforeignkeysource/999/', 'name': 'source-4', 'target': ''} + data = {'url': 'http://testserver/nullableforeignkeysource/4/', 'name': 'source-4', 'target': ''} + expected_data = {'url': 'http://testserver/nullableforeignkeysource/4/', 'name': 'source-4', 'target': None} serializer = NullableForeignKeySourceSerializer(data=data, context={'request': request}) assert serializer.is_valid() obj = serializer.save() - expected_data = {'url': _nfk_source_url(obj.pk), 'name': 'source-4', 'target': None} assert serializer.data == expected_data assert obj.name == 'source-4' @@ -434,16 +409,16 @@ def test_foreign_key_create_with_valid_emptystring(self): queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': _fk_target_url(self.target1.pk)}, - {'url': _nfk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, - {'url': _nfk_source_url(self.s3.pk), 'name': 'source-3', 'target': None}, - {'url': _nfk_source_url(obj.pk), 'name': 'source-4', 'target': None} + {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/nullableforeignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None}, + {'url': 'http://testserver/nullableforeignkeysource/4/', 'name': 'source-4', 'target': None} ] assert serializer.data == expected def test_foreign_key_update_with_valid_null(self): - data = {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': None} - instance = NullableForeignKeySource.objects.get(pk=self.s1.pk) + data = {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=1) serializer = NullableForeignKeySourceSerializer(instance, data=data, context={'request': request}) assert serializer.is_valid() serializer.save() @@ -453,9 +428,9 @@ def test_foreign_key_update_with_valid_null(self): queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': None}, - {'url': _nfk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, - {'url': _nfk_source_url(self.s3.pk), 'name': 'source-3', 'target': None}, + {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': None}, + {'url': 'http://testserver/nullableforeignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None}, ] assert serializer.data == expected @@ -464,9 +439,9 @@ def test_foreign_key_update_with_valid_emptystring(self): The emptystring should be interpreted as null in the context of relationships. """ - data = {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': ''} - expected_data = {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': None} - instance = NullableForeignKeySource.objects.get(pk=self.s1.pk) + data = {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': ''} + expected_data = {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=1) serializer = NullableForeignKeySourceSerializer(instance, data=data, context={'request': request}) assert serializer.is_valid() serializer.save() @@ -476,9 +451,9 @@ def test_foreign_key_update_with_valid_emptystring(self): queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _nfk_source_url(self.s1.pk), 'name': 'source-1', 'target': None}, - {'url': _nfk_source_url(self.s2.pk), 'name': 'source-2', 'target': _fk_target_url(self.target1.pk)}, - {'url': _nfk_source_url(self.s3.pk), 'name': 'source-3', 'target': None}, + {'url': 'http://testserver/nullableforeignkeysource/1/', 'name': 'source-1', 'target': None}, + {'url': 'http://testserver/nullableforeignkeysource/2/', 'name': 'source-2', 'target': 'http://testserver/foreignkeytarget/1/'}, + {'url': 'http://testserver/nullableforeignkeysource/3/', 'name': 'source-3', 'target': None}, ] assert serializer.data == expected @@ -486,6 +461,7 @@ def test_foreign_key_update_with_valid_emptystring(self): @override_settings(ROOT_URLCONF='tests.test_relations_hyperlink') class HyperlinkedNullableOneToOneTests(TestCase): def setUp(self): + _reset_sequences(OneToOneTarget, NullableOneToOneSource) target = OneToOneTarget(name='target-1') target.save() new_target = OneToOneTarget(name='target-2') @@ -493,15 +469,11 @@ def setUp(self): source = NullableOneToOneSource(name='source-1', target=target) source.save() - self.target1 = target - self.target2 = new_target - self.source1 = source - def test_reverse_foreign_key_retrieve_with_null(self): queryset = OneToOneTarget.objects.order_by('pk') serializer = NullableOneToOneTargetSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': _o2o_target_url(self.target1.pk), 'name': 'target-1', 'nullable_source': _o2o_source_url(self.source1.pk)}, - {'url': _o2o_target_url(self.target2.pk), 'name': 'target-2', 'nullable_source': None}, + {'url': 'http://testserver/onetoonetarget/1/', 'name': 'target-1', 'nullable_source': 'http://testserver/nullableonetoonesource/1/'}, + {'url': 'http://testserver/onetoonetarget/2/', 'name': 'target-2', 'nullable_source': None}, ] assert serializer.data == expected From 8064ef6cf0fa5b6af445479df5c359816b20c96d Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 28 Apr 2026 21:55:10 +0100 Subject: [PATCH 08/12] Reset sequences in a project-wide fixture and revert most changes --- tests/conftest.py | 29 ++++ tests/test_filters.py | 139 +++++++++--------- tests/test_generics.py | 101 ++++++------- tests/test_model_serializer.py | 10 +- tests/test_permissions.py | 104 +++++++------- tests/test_prefetch_related.py | 4 +- tests/test_relations_hyperlink.py | 21 --- tests/test_relations_pk.py | 227 ++++++++++++++---------------- tests/test_relations_slug.py | 164 ++++++++++----------- tests/test_validators.py | 7 +- 10 files changed, 386 insertions(+), 420 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index db1b79254e..9257fb05de 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,36 @@ import dj_database_url import django import pytest +from django.apps import apps from django.core import management +from django.core.management.color import no_style +from django.db import connection +from django.test import TestCase, TransactionTestCase + + +@pytest.fixture(autouse=True) +def _reset_sequences(request): + """Reset all database sequences so PKs start from 1 in each test. + + PostgreSQL sequences are non-transactional and persist across + TestCase's transaction rollbacks. This fixture ensures every test + gets predictable PKs starting from 1 regardless of execution order. + No-op on SQLite and skipped for tests that don't use the database. + """ + if connection.vendor != 'postgresql': + return + # Only run for tests that actually have database access. + if not (request.cls and issubclass(request.cls, (TestCase, TransactionTestCase))): + if 'db' not in request.fixturenames and 'transactional_db' not in request.fixturenames: + return + + table_names = set(connection.introspection.table_names()) + models = [m for m in apps.get_models() if m._meta.db_table in table_names] + sql_list = connection.ops.sequence_reset_sql(no_style(), models) + if sql_list: + with connection.cursor() as cursor: + for sql in sql_list: + cursor.execute(sql) def pytest_addoption(parser): diff --git a/tests/test_filters.py b/tests/test_filters.py index 8bae8d30fe..36ffc08aea 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -45,7 +45,7 @@ def test_filter_queryset_raises_error(self): class SearchFilterModel(models.Model): - title = models.CharField(max_length=22) + title = models.CharField(max_length=25) text = models.CharField(max_length=100) @@ -64,7 +64,6 @@ def setUpTestData(cls): # zz bcd # zzz cde # ... - cls.objects_by_title = {} for idx in range(10): title = 'z' * (idx + 1) text = ( @@ -72,11 +71,10 @@ def setUpTestData(cls): chr(idx + ord('b')) + chr(idx + ord('c')) ) - obj = SearchFilterModel.objects.create(title=title, text=text) - cls.objects_by_title[title] = obj + SearchFilterModel(title=title, text=text).save() - cls.obj_a_title = SearchFilterModel.objects.create(title='A title', text='The long text') - cls.obj_the_title = SearchFilterModel.objects.create(title='The title', text='The "text') + SearchFilterModel(title='A title', text='The long text').save() + SearchFilterModel(title='The title', text='The "text').save() def test_search(self): class SearchListView(generics.ListAPIView): @@ -89,8 +87,8 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': 'b'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['z'].pk, 'title': 'z', 'text': 'abc'}, - {'id': self.objects_by_title['zz'].pk, 'title': 'zz', 'text': 'bcd'} + {'id': 1, 'title': 'z', 'text': 'abc'}, + {'id': 2, 'title': 'zz', 'text': 'bcd'} ] def test_search_returns_same_queryset_if_no_search_fields_or_terms_provided(self): @@ -117,7 +115,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': 'zzz'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['zzz'].pk, 'title': 'zzz', 'text': 'cde'} + {'id': 3, 'title': 'zzz', 'text': 'cde'} ] def test_startswith_search(self): @@ -131,7 +129,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': 'b'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['zz'].pk, 'title': 'zz', 'text': 'bcd'} + {'id': 2, 'title': 'zz', 'text': 'bcd'} ] def test_regexp_search(self): @@ -145,7 +143,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': 'z{2} ^b'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['zz'].pk, 'title': 'zz', 'text': 'bcd'} + {'id': 2, 'title': 'zz', 'text': 'bcd'} ] def test_search_with_nonstandard_search_param(self): @@ -162,8 +160,8 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'query': 'b'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['z'].pk, 'title': 'z', 'text': 'abc'}, - {'id': self.objects_by_title['zz'].pk, 'title': 'zz', 'text': 'bcd'} + {'id': 1, 'title': 'z', 'text': 'abc'}, + {'id': 2, 'title': 'zz', 'text': 'bcd'} ] reload_module(filters) @@ -190,7 +188,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': r'^\w{3}$', 'title_only': 'true'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['zzz'].pk, 'title': 'zzz', 'text': 'cde'} + {'id': 3, 'title': 'zzz', 'text': 'cde'} ] def test_search_field_with_null_characters(self): @@ -211,7 +209,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': 'c'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['z'].pk, 'title': 'z', 'text': 'abc'}, + {'id': 1, 'title': 'z', 'text': 'abc'}, ] def test_search_field_with_additional_transforms(self): @@ -245,8 +243,8 @@ def as_sql(self, compiler, connection): request = factory.get('/', {'search': 'bc'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['z'].pk, 'title': 'z', 'text': 'abc'}, - {'id': self.objects_by_title['zz'].pk, 'title': 'zz', 'text': 'bcd'}, + {'id': 1, 'title': 'z', 'text': 'abc'}, + {'id': 2, 'title': 'zz', 'text': 'bcd'}, ] def test_search_field_with_multiple_words(self): @@ -276,7 +274,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': '"\\\"text"'}) response = view(request) assert response.data == [ - {'id': self.obj_the_title.pk, 'title': 'The title', 'text': 'The "text'}, + {'id': 12, 'title': 'The title', 'text': 'The "text'}, ] def test_search_field_with_quotes(self): @@ -289,7 +287,7 @@ class SearchListView(generics.ListAPIView): request = factory.get('/', {'search': '"long text"'}) response = view(request) assert response.data == [ - {'id': self.obj_a_title.pk, 'title': 'A title', 'text': 'The long text'}, + {'id': 11, 'title': 'A title', 'text': 'The long text'}, ] @@ -465,14 +463,13 @@ class SearchFilterM2MTests(TestCase): def setUp(self): # Sequence of title/text/attributes is: # - # z abc [attr1, attr2, attr3] - # zz bcd [attr1, attr2, attr3] - # zzz cde [attr1, attr2, attr3] + # z abc [1, 2, 3] + # zz bcd [1, 2, 3] + # zzz cde [1, 2, 3] # ... - self.attributes = [] for idx in range(3): label = 'w' * (idx + 1) - self.attributes.append(AttributeModel.objects.create(label=label)) + AttributeModel.objects.create(label=label) for idx in range(10): title = 'z' * (idx + 1) @@ -482,7 +479,7 @@ def setUp(self): chr(idx + ord('c')) ) SearchFilterModelM2M(title=title, text=text).save() - SearchFilterModelM2M.objects.get(title='zz').attributes.add(*self.attributes) + SearchFilterModelM2M.objects.get(title='zz').attributes.add(1, 2, 3) def test_m2m_search(self): class SearchListView(generics.ListAPIView): @@ -667,7 +664,6 @@ def setUp(self): # zyx abc # yxw bcd # xwv cde - self.objects_by_title = {} for idx in range(3): title = ( chr(ord('z') - idx) + @@ -679,8 +675,7 @@ def setUp(self): chr(idx + ord('b')) + chr(idx + ord('c')) ) - obj = OrderingFilterModel.objects.create(title=title, text=text) - self.objects_by_title[title] = obj + OrderingFilterModel(title=title, text=text).save() def test_ordering(self): class OrderingListView(generics.ListAPIView): @@ -694,9 +689,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'ordering': 'text'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, - {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, - {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, + {'id': 1, 'title': 'zyx', 'text': 'abc'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd'}, + {'id': 3, 'title': 'xwv', 'text': 'cde'}, ] def test_reverse_ordering(self): @@ -711,9 +706,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'ordering': '-text'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, - {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, - {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, + {'id': 3, 'title': 'xwv', 'text': 'cde'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd'}, + {'id': 1, 'title': 'zyx', 'text': 'abc'}, ] def test_incorrecturl_extrahyphens_ordering(self): @@ -728,9 +723,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'ordering': '--text'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, - {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, - {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, + {'id': 3, 'title': 'xwv', 'text': 'cde'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd'}, + {'id': 1, 'title': 'zyx', 'text': 'abc'}, ] def test_incorrectfield_ordering(self): @@ -745,9 +740,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'ordering': 'foobar'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, - {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, - {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, + {'id': 3, 'title': 'xwv', 'text': 'cde'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd'}, + {'id': 1, 'title': 'zyx', 'text': 'abc'}, ] def test_ordering_without_ordering_fields(self): @@ -763,27 +758,27 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'ordering': 'text'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, - {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, - {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, + {'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, + {'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, ] # `incorrectfield` ordering works fine. request = factory.get('/', {'ordering': 'foobar'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, - {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, - {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, + {'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, + {'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, ] # `description` is a Model property, which should be ignored. request = factory.get('/', {'ordering': 'description'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, - {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, - {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, + {'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, + {'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, ] def test_default_ordering(self): @@ -798,9 +793,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('') response = view(request) assert response.data == [ - {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, - {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, - {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, + {'id': 3, 'title': 'xwv', 'text': 'cde'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd'}, + {'id': 1, 'title': 'zyx', 'text': 'abc'}, ] def test_default_ordering_using_string(self): @@ -815,9 +810,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('') response = view(request) assert response.data == [ - {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, - {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, - {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, + {'id': 3, 'title': 'xwv', 'text': 'cde'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd'}, + {'id': 1, 'title': 'zyx', 'text': 'abc'}, ] def test_ordering_by_aggregate_field(self): @@ -843,9 +838,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'ordering': 'related__count'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, - {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, - {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, + {'id': 1, 'title': 'zyx', 'text': 'abc'}, + {'id': 3, 'title': 'xwv', 'text': 'cde'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd'}, ] def test_ordering_by_dotted_source(self): @@ -893,9 +888,9 @@ class OrderingListView(generics.ListAPIView): request = factory.get('/', {'order': 'text'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, - {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, - {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, + {'id': 1, 'title': 'zyx', 'text': 'abc'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd'}, + {'id': 3, 'title': 'xwv', 'text': 'cde'}, ] reload_module(filters) @@ -928,9 +923,9 @@ def get_serializer_class(self): request = factory.get('/', {'ordering': 'text'}) response = view(request) assert response.data == [ - {'id': self.objects_by_title['zyx'].pk, 'title': 'zyx', 'text': 'abc'}, - {'id': self.objects_by_title['yxw'].pk, 'title': 'yxw', 'text': 'bcd'}, - {'id': self.objects_by_title['xwv'].pk, 'title': 'xwv', 'text': 'cde'}, + {'id': 1, 'title': 'zyx', 'text': 'abc'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd'}, + {'id': 3, 'title': 'xwv', 'text': 'cde'}, ] def test_ordering_with_improper_configuration(self): @@ -981,12 +976,10 @@ class Meta: class SensitiveOrderingFilterTests(TestCase): def setUp(self): - self.objects_by_username = {} for idx in range(3): username = {0: 'userA', 1: 'userB', 2: 'userC'}[idx] password = {0: 'passA', 1: 'passC', 2: 'passB'}[idx] - obj = SensitiveOrderingFilterModel.objects.create(username=username, password=password) - self.objects_by_username[username] = obj + SensitiveOrderingFilterModel(username=username, password=password).save() def test_order_by_serializer_fields(self): for serializer_cls in [ @@ -1010,9 +1003,9 @@ class OrderingListView(generics.ListAPIView): # Note: Inverse username ordering correctly applied. assert response.data == [ - {'id': self.objects_by_username['userC'].pk, username_field: 'userC'}, - {'id': self.objects_by_username['userB'].pk, username_field: 'userB'}, - {'id': self.objects_by_username['userA'].pk, username_field: 'userA'}, + {'id': 3, username_field: 'userC'}, + {'id': 2, username_field: 'userB'}, + {'id': 1, username_field: 'userA'}, ] def test_cannot_order_by_non_serializer_fields(self): @@ -1037,7 +1030,7 @@ class OrderingListView(generics.ListAPIView): # Note: The passwords are not in order. Default ordering is used. assert response.data == [ - {'id': self.objects_by_username['userA'].pk, username_field: 'userA'}, # PassB - {'id': self.objects_by_username['userB'].pk, username_field: 'userB'}, # PassC - {'id': self.objects_by_username['userC'].pk, username_field: 'userC'}, # PassA + {'id': 1, username_field: 'userA'}, # PassB + {'id': 2, username_field: 'userB'}, # PassC + {'id': 3, username_field: 'userC'}, # PassA ] diff --git a/tests/test_generics.py b/tests/test_generics.py index e8a1c142ff..25b96fcbb2 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -119,8 +119,9 @@ def test_post_root_view(self): with self.assertNumQueries(1): response = self.view(request).render() assert response.status_code == status.HTTP_201_CREATED - created = self.objects.get(text='foobar') - assert response.data == {'id': created.pk, 'text': 'foobar'} + assert response.data == {'id': 4, 'text': 'foobar'} + created = self.objects.get(id=4) + assert created.text == 'foobar' def test_put_root_view(self): """ @@ -152,8 +153,9 @@ def test_post_cannot_set_id(self): with self.assertNumQueries(1): response = self.view(request).render() assert response.status_code == status.HTTP_201_CREATED - created = self.objects.get(text='foobar') - assert response.data == {'id': created.pk, 'text': 'foobar'} + assert response.data == {'id': 4, 'text': 'foobar'} + created = self.objects.get(id=4) + assert created.text == 'foobar' def test_post_error_root_view(self): """ @@ -175,11 +177,8 @@ def setUp(self): Create 3 BasicModel instances. """ items = ['foo', 'bar', 'baz', 'filtered out'] - self.created_items = [] for item in items: - obj = BasicModel.objects.create(text=item) - if item != 'filtered out': - self.created_items.append(obj) + BasicModel(text=item).save() self.objects = BasicModel.objects.exclude(text='filtered out') self.data = [ {'id': obj.id, 'text': obj.text} @@ -192,10 +191,9 @@ def test_get_instance_view(self): """ GET requests to RetrieveUpdateDestroyAPIView should return a single object. """ - pk = self.created_items[0].pk - request = factory.get(f'/{pk}') + request = factory.get('/1') with self.assertNumQueries(1): - response = self.view(request, pk=pk).render() + response = self.view(request, pk=1).render() assert response.status_code == status.HTTP_200_OK assert response.data == self.data[0] @@ -214,43 +212,40 @@ 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(f'/{pk}', data, format='json') + request = factory.put('/1', data, format='json') with self.assertNumQueries(EXPECTED_QUERIES_FOR_PUT): - response = self.view(request, pk=str(pk)).render() + response = self.view(request, pk='1').render() assert response.status_code == status.HTTP_200_OK - assert dict(response.data) == {'id': pk, 'text': 'foobar'} - updated = self.objects.get(id=pk) + assert dict(response.data) == {'id': 1, 'text': 'foobar'} + updated = self.objects.get(id=1) 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(f'/{pk}', data, format='json') + request = factory.patch('/1', data, format='json') with self.assertNumQueries(EXPECTED_QUERIES_FOR_PUT): - response = self.view(request, pk=pk).render() + response = self.view(request, pk=1).render() assert response.status_code == status.HTTP_200_OK - assert response.data == {'id': pk, 'text': 'foobar'} - updated = self.objects.get(id=pk) + assert response.data == {'id': 1, 'text': 'foobar'} + updated = self.objects.get(id=1) assert updated.text == 'foobar' def test_delete_instance_view(self): """ DELETE requests to RetrieveUpdateDestroyAPIView should delete an object. """ - pk = self.created_items[0].pk - request = factory.delete(f'/{pk}') + request = factory.delete('/1') with self.assertNumQueries(2): - response = self.view(request, pk=pk).render() + response = self.view(request, pk=1).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 == [self.created_items[1].pk, self.created_items[2].pk] + assert ids == [2, 3] def test_get_instance_view_incorrect_arg(self): """ @@ -266,14 +261,13 @@ 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(f'/{pk}', data, format='json') + request = factory.put('/1', data, format='json') with self.assertNumQueries(EXPECTED_QUERIES_FOR_PUT): - response = self.view(request, pk=pk).render() + response = self.view(request, pk=1).render() assert response.status_code == status.HTTP_200_OK - assert response.data == {'id': pk, 'text': 'foobar'} - updated = self.objects.get(id=pk) + assert response.data == {'id': 1, 'text': 'foobar'} + updated = self.objects.get(id=1) assert updated.text == 'foobar' def test_put_to_deleted_instance(self): @@ -281,12 +275,11 @@ def test_put_to_deleted_instance(self): PUT requests to RetrieveUpdateDestroyAPIView should return 404 if an object does not currently exist. """ - pk = self.created_items[0].pk - self.objects.get(id=pk).delete() + self.objects.get(id=1).delete() data = {'text': 'foobar'} - request = factory.put(f'/{pk}', data, format='json') + request = factory.put('/1', data, format='json') with self.assertNumQueries(1): - response = self.view(request, pk=pk).render() + response = self.view(request, pk=1).render() assert response.status_code == status.HTTP_404_NOT_FOUND def test_put_to_filtered_out_instance(self): @@ -305,21 +298,19 @@ def test_patch_cannot_create_an_object(self): PATCH requests should not be able to create objects. """ data = {'text': 'foobar'} - non_existent_pk = 999999 - request = factory.patch(f'/{non_existent_pk}', data, format='json') + request = factory.patch('/999', data, format='json') with self.assertNumQueries(1): - response = self.view(request, pk=non_existent_pk).render() + response = self.view(request, pk=999).render() assert response.status_code == status.HTTP_404_NOT_FOUND - assert not self.objects.filter(id=non_existent_pk).exists() + assert not self.objects.filter(id=999).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=pk).render() + response = self.view(request, pk=1).render() expected_error = 'Ensure this field has no more than 100 characters.' assert expected_error in response.rendered_content.decode() @@ -354,10 +345,8 @@ def setUp(self): Create 3 BasicModel instances. """ items = ['foo', 'bar', 'baz'] - self.created_items = [] for item in items: - obj = BasicModel.objects.create(text=item) - self.created_items.append(obj) + BasicModel(text=item).save() self.objects = BasicModel.objects self.data = [ {'id': obj.id, 'text': obj.text} @@ -380,10 +369,9 @@ def test_overridden_get_object_view(self): """ GET requests to RetrieveUpdateDestroyAPIView should return a single object. """ - pk = self.created_items[0].pk - request = factory.get(f'/{pk}') + request = factory.get('/1') with self.assertNumQueries(1): - response = self.view(request, pk=pk).render() + response = self.view(request, pk=1).render() assert response.status_code == status.HTTP_200_OK assert response.data == self.data[0] @@ -416,7 +404,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(content='foobar') + created = self.objects.get(id=1) assert created.content == 'foobar' @@ -495,10 +483,8 @@ def setUp(self): Create 3 BasicModel instances to filter on. """ items = ['foo', 'bar', 'baz'] - self.created_items = [] for item in items: - obj = BasicModel.objects.create(text=item) - self.created_items.append(obj) + BasicModel(text=item).save() self.objects = BasicModel.objects self.data = [ {'id': obj.id, 'text': obj.text} @@ -514,8 +500,7 @@ 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 - foo_obj = self.created_items[0] - assert response.data == [{'id': foo_obj.pk, 'text': 'foo'}] + assert response.data == [{'id': 1, 'text': 'foo'}] def test_get_root_view_filters_out_all_models_with_exclusive_filter_backend(self): """ @@ -531,10 +516,9 @@ 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(f'/{pk}') - response = instance_view(request, pk=pk).render() + request = factory.get('/1') + response = instance_view(request, pk=1).render() assert response.status_code == status.HTTP_404_NOT_FOUND assert response.data == { 'detail': ErrorDetail( @@ -547,12 +531,11 @@ 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(f'/{foo_obj.pk}') - response = instance_view(request, pk=foo_obj.pk).render() + request = factory.get('/1') + response = instance_view(request, pk=1).render() assert response.status_code == status.HTTP_200_OK - assert response.data == {'id': foo_obj.pk, 'text': 'foo'} + assert response.data == {'id': 1, 'text': 'foo'} def test_dynamic_serializer_form_in_browsable_api(self): """ diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 702cabdd89..944c1ba278 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -760,7 +760,7 @@ class DisplayValueModel(models.Model): class TestRelationalFieldDisplayValue(TestCase): def setUp(self): - self.targets = DisplayValueTargetModel.objects.bulk_create([ + DisplayValueTargetModel.objects.bulk_create([ DisplayValueTargetModel(name='Red'), DisplayValueTargetModel(name='Yellow'), DisplayValueTargetModel(name='Green'), @@ -773,7 +773,7 @@ class Meta: fields = '__all__' serializer = TestSerializer() - expected = {t.pk: '%s Color' % t.name for t in self.targets} + expected = {1: 'Red Color', 2: 'Yellow Color', 3: 'Green Color'} self.assertEqual(serializer.fields['color'].choices, expected) def test_custom_display_value(self): @@ -789,7 +789,7 @@ class Meta: fields = '__all__' serializer = TestSerializer() - expected = {t.pk: 'My %s Color' % t.name for t in self.targets} + expected = {1: 'My Red Color', 2: 'My Yellow Color', 3: 'My Green Color'} self.assertEqual(serializer.fields['color'].choices, expected) @@ -1278,10 +1278,10 @@ class Meta: parent_serializer = TestParentModelSerializer(parent) child_serializer = TestChildModelSerializer(child) - parent_expected = {'children': ['def'], 'id': parent.pk, 'title': 'abc'} + parent_expected = {'children': ['def'], 'id': 1, 'title': 'abc'} self.assertEqual(parent_serializer.data, parent_expected) - child_expected = {'parent': parent.pk, 'value': 'def'} + child_expected = {'parent': 1, 'value': 'def'} self.assertEqual(child_serializer.data, child_expected) diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 652cf6162a..93fe7b9410 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -92,12 +92,12 @@ def setUp(self): self.disallowed_credentials = basic_auth_header('disallowed', 'password') self.updateonly_credentials = basic_auth_header('updateonly', 'password') - self.instance = BasicModel.objects.create(text='foo') + BasicModel(text='foo').save() def test_has_create_permissions(self): request = factory.post('/', {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.permitted_credentials) - response = root_view(request, pk=self.instance.pk) + response = root_view(request, pk=1) self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_api_root_view_discard_default_django_model_permission(self): @@ -136,35 +136,35 @@ def test_ignore_model_permissions_with_authenticated_user(self): def test_get_queryset_has_create_permissions(self): request = factory.post('/', {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.permitted_credentials) - response = get_queryset_list_view(request, pk=self.instance.pk) + response = get_queryset_list_view(request, pk=1) self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_has_put_permissions(self): - request = factory.put('/%d' % self.instance.pk, {'text': 'foobar'}, format='json', + request = factory.put('/1', {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.permitted_credentials) - response = instance_view(request, pk=self.instance.pk) + response = instance_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) def test_has_delete_permissions(self): - request = factory.delete('/%d' % self.instance.pk, HTTP_AUTHORIZATION=self.permitted_credentials) - response = instance_view(request, pk=self.instance.pk) + request = factory.delete('/1', HTTP_AUTHORIZATION=self.permitted_credentials) + response = instance_view(request, pk=1) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) def test_does_not_have_create_permissions(self): request = factory.post('/', {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.disallowed_credentials) - response = root_view(request, pk=self.instance.pk) + response = root_view(request, pk=1) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_does_not_have_put_permissions(self): - request = factory.put('/%d' % self.instance.pk, {'text': 'foobar'}, format='json', + request = factory.put('/1', {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.disallowed_credentials) - response = instance_view(request, pk=self.instance.pk) + response = instance_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_does_not_have_delete_permissions(self): - request = factory.delete('/%d' % self.instance.pk, HTTP_AUTHORIZATION=self.disallowed_credentials) - response = instance_view(request, pk=self.instance.pk) + request = factory.delete('/1', HTTP_AUTHORIZATION=self.disallowed_credentials) + response = instance_view(request, pk=1) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_options_permitted(self): @@ -172,16 +172,16 @@ def test_options_permitted(self): '/', HTTP_AUTHORIZATION=self.permitted_credentials ) - response = root_view(request, pk=self.instance.pk) + response = root_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('actions', response.data) self.assertEqual(list(response.data['actions']), ['POST']) request = factory.options( - '/%d' % self.instance.pk, + '/1', HTTP_AUTHORIZATION=self.permitted_credentials ) - response = instance_view(request, pk=self.instance.pk) + response = instance_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('actions', response.data) self.assertEqual(list(response.data['actions']), ['PUT']) @@ -191,15 +191,15 @@ def test_options_disallowed(self): '/', HTTP_AUTHORIZATION=self.disallowed_credentials ) - response = root_view(request, pk=self.instance.pk) + response = root_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertNotIn('actions', response.data) request = factory.options( - '/%d' % self.instance.pk, + '/1', HTTP_AUTHORIZATION=self.disallowed_credentials ) - response = instance_view(request, pk=self.instance.pk) + response = instance_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertNotIn('actions', response.data) @@ -208,22 +208,22 @@ def test_options_updateonly(self): '/', HTTP_AUTHORIZATION=self.updateonly_credentials ) - response = root_view(request, pk=self.instance.pk) + response = root_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertNotIn('actions', response.data) request = factory.options( - '/%d' % self.instance.pk, + '/1', HTTP_AUTHORIZATION=self.updateonly_credentials ) - response = instance_view(request, pk=self.instance.pk) + response = instance_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('actions', response.data) self.assertEqual(list(response.data['actions']), ['PUT']) def test_empty_view_does_not_assert(self): - request = factory.get('/%d' % self.instance.pk, HTTP_AUTHORIZATION=self.permitted_credentials) - response = empty_list_view(request, pk=self.instance.pk) + request = factory.get('/1', HTTP_AUTHORIZATION=self.permitted_credentials) + response = empty_list_view(request, pk=1) self.assertEqual(response.status_code, status.HTTP_200_OK) def test_calling_method_not_allowed(self): @@ -231,8 +231,8 @@ def test_calling_method_not_allowed(self): response = root_view(request) self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) - request = factory.generic('METHOD_NOT_ALLOWED', '/%d' % self.instance.pk, HTTP_AUTHORIZATION=self.permitted_credentials) - response = instance_view(request, pk=self.instance.pk) + request = factory.generic('METHOD_NOT_ALLOWED', '/1', HTTP_AUTHORIZATION=self.permitted_credentials) + response = instance_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) def test_check_auth_before_queryset_call(self): @@ -362,11 +362,11 @@ def setUp(self): writers = Group.objects.create(name='writers') deleters = Group.objects.create(name='deleters') - self.model = BasicPermModel.objects.create(text='foo') + model = BasicPermModel.objects.create(text='foo') - assign_perm(perms['view'], readers, self.model) - assign_perm(perms['change'], writers, self.model) - assign_perm(perms['delete'], deleters, self.model) + assign_perm(perms['view'], readers, model) + assign_perm(perms['change'], writers, model) + assign_perm(perms['delete'], deleters, model) readers.user_set.add(users['fullaccess'], users['readonly']) writers.user_set.add(users['fullaccess'], users['writeonly']) @@ -378,50 +378,50 @@ def setUp(self): # Delete def test_can_delete_permissions(self): - request = factory.delete('/%d' % self.model.pk, HTTP_AUTHORIZATION=self.credentials['deleteonly']) - response = object_permissions_view(request, pk=self.model.pk) + request = factory.delete('/1', HTTP_AUTHORIZATION=self.credentials['deleteonly']) + response = object_permissions_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) def test_cannot_delete_permissions(self): - request = factory.delete('/%d' % self.model.pk, HTTP_AUTHORIZATION=self.credentials['readonly']) - response = object_permissions_view(request, pk=self.model.pk) + request = factory.delete('/1', HTTP_AUTHORIZATION=self.credentials['readonly']) + response = object_permissions_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) # Update def test_can_update_permissions(self): request = factory.patch( - '/%d' % self.model.pk, {'text': 'foobar'}, format='json', + '/1', {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.credentials['writeonly'] ) - response = object_permissions_view(request, pk=self.model.pk) + response = object_permissions_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data.get('text'), 'foobar') def test_cannot_update_permissions(self): request = factory.patch( - '/%d' % self.model.pk, {'text': 'foobar'}, format='json', + '/1', {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.credentials['deleteonly'] ) - response = object_permissions_view(request, pk=self.model.pk) + response = object_permissions_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_cannot_update_permissions_non_existing(self): request = factory.patch( - '/999999', {'text': 'foobar'}, format='json', + '/999', {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.credentials['deleteonly'] ) - response = object_permissions_view(request, pk='999999') + response = object_permissions_view(request, pk='999') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) # Read def test_can_read_permissions(self): - request = factory.get('/%d' % self.model.pk, HTTP_AUTHORIZATION=self.credentials['readonly']) - response = object_permissions_view(request, pk=self.model.pk) + request = factory.get('/1', HTTP_AUTHORIZATION=self.credentials['readonly']) + response = object_permissions_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) def test_cannot_read_permissions(self): - request = factory.get('/%d' % self.model.pk, HTTP_AUTHORIZATION=self.credentials['writeonly']) - response = object_permissions_view(request, pk=self.model.pk) + request = factory.get('/1', HTTP_AUTHORIZATION=self.credentials['writeonly']) + response = object_permissions_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_can_read_get_queryset_permissions(self): @@ -429,8 +429,8 @@ def test_can_read_get_queryset_permissions(self): same as ``test_can_read_permissions`` but with a view that rely on ``.get_queryset()`` instead of ``.queryset``. """ - request = factory.get('/%d' % self.model.pk, HTTP_AUTHORIZATION=self.credentials['readonly']) - response = get_queryset_object_permissions_view(request, pk=self.model.pk) + request = factory.get('/1', HTTP_AUTHORIZATION=self.credentials['readonly']) + response = get_queryset_object_permissions_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) # Read list @@ -440,7 +440,7 @@ def test_can_read_list_permissions(self): request = factory.get('/', HTTP_AUTHORIZATION=self.credentials['readonly']) response = object_permissions_list_view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data[0].get('id'), self.model.pk) + self.assertEqual(response.data[0].get('id'), 1) def test_cannot_method_not_allowed(self): request = factory.generic('METHOD_NOT_ALLOWED', '/', HTTP_AUTHORIZATION=self.credentials['readonly']) @@ -506,36 +506,36 @@ class DeniedObjectViewWithDetail(PermissionInstanceView): class CustomPermissionsTests(TestCase): def setUp(self): - self.instance = BasicModel.objects.create(text='foo') + BasicModel(text='foo').save() User.objects.create_user('username', 'username@example.com', 'password') credentials = basic_auth_header('username', 'password') - self.request = factory.get('/%d' % self.instance.pk, format='json', HTTP_AUTHORIZATION=credentials) + self.request = factory.get('/1', format='json', HTTP_AUTHORIZATION=credentials) self.custom_message = 'Custom: You cannot access this resource' self.custom_code = 'permission_denied_custom' def test_permission_denied(self): - response = denied_view(self.request, pk=self.instance.pk) + response = denied_view(self.request, pk=1) detail = response.data.get('detail') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertNotEqual(detail, self.custom_message) self.assertNotEqual(detail.code, self.custom_code) def test_permission_denied_with_custom_detail(self): - response = denied_view_with_detail(self.request, pk=self.instance.pk) + response = denied_view_with_detail(self.request, pk=1) detail = response.data.get('detail') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(detail, self.custom_message) self.assertEqual(detail.code, self.custom_code) def test_permission_denied_for_object(self): - response = denied_object_view(self.request, pk=self.instance.pk) + response = denied_object_view(self.request, pk=1) detail = response.data.get('detail') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertNotEqual(detail, self.custom_message) self.assertNotEqual(detail.code, self.custom_code) def test_permission_denied_for_object_with_custom_detail(self): - response = denied_object_view_with_detail(self.request, pk=self.instance.pk) + response = denied_object_view_with_detail(self.request, pk=1) detail = response.data.get('detail') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(detail, self.custom_message) diff --git a/tests/test_prefetch_related.py b/tests/test_prefetch_related.py index 29b81249f1..12ecbf2e6a 100644 --- a/tests/test_prefetch_related.py +++ b/tests/test_prefetch_related.py @@ -34,7 +34,7 @@ def test_prefetch_related_updates(self): expected = { 'id': pk, 'username': 'new', - 'groups': [groups_pk], + 'groups': [1], 'email': 'tom@example.com' } assert response.data == expected @@ -52,7 +52,7 @@ def test_prefetch_related_excluding_instance_from_original_queryset(self): expected = { 'id': pk, 'username': 'exclude', - 'groups': [groups_pk], + 'groups': [1], 'email': 'tom@example.com' } assert response.data == expected diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py index aa1c930a31..4be0b41bdc 100644 --- a/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -1,6 +1,4 @@ import pytest -from django.core.management.color import no_style -from django.db import connection from django.test import TestCase, override_settings from django.urls import path @@ -71,25 +69,9 @@ class Meta: fields = ('url', 'name', 'nullable_source') -def _reset_sequences(*models): - """Reset database sequences for the given models so PKs start from 1. - - PostgreSQL sequence operations are non-transactional, so this works - even inside TestCase's transaction wrapper. No-op on SQLite. - """ - if connection.vendor != 'postgresql': - return - sql_list = connection.ops.sequence_reset_sql(no_style(), models) - if sql_list: - with connection.cursor() as cursor: - for sql in sql_list: - cursor.execute(sql) - - @override_settings(ROOT_URLCONF='tests.test_relations_hyperlink') class HyperlinkedManyToManyTests(TestCase): def setUp(self): - _reset_sequences(ManyToManySource, ManyToManyTarget) for idx in range(1, 4): target = ManyToManyTarget(name='target-%d' % idx) target.save() @@ -224,7 +206,6 @@ def test_data_cannot_be_accessed_prior_to_is_valid(self): @override_settings(ROOT_URLCONF='tests.test_relations_hyperlink') class HyperlinkedForeignKeyTests(TestCase): def setUp(self): - _reset_sequences(ForeignKeySource, ForeignKeyTarget) target = ForeignKeyTarget(name='target-1') target.save() new_target = ForeignKeyTarget(name='target-2') @@ -354,7 +335,6 @@ def test_foreign_key_update_with_invalid_null(self): @override_settings(ROOT_URLCONF='tests.test_relations_hyperlink') class HyperlinkedNullableForeignKeyTests(TestCase): def setUp(self): - _reset_sequences(NullableForeignKeySource, ForeignKeyTarget) target = ForeignKeyTarget(name='target-1') target.save() for idx in range(1, 4): @@ -461,7 +441,6 @@ def test_foreign_key_update_with_valid_emptystring(self): @override_settings(ROOT_URLCONF='tests.test_relations_hyperlink') class HyperlinkedNullableOneToOneTests(TestCase): def setUp(self): - _reset_sequences(OneToOneTarget, NullableOneToOneSource) target = OneToOneTarget(name='target-1') target.save() new_target = OneToOneTarget(name='target-2') diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index 0393844a00..a7af8ffd1d 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -97,15 +97,11 @@ class Meta: class PKManyToManyTests(TestCase): def setUp(self): - self.targets = [] - self.sources = [] for idx in range(1, 4): target = ManyToManyTarget(name='target-%d' % idx) target.save() - self.targets.append(target) source = ManyToManySource(name='source-%d' % idx) source.save() - self.sources.append(source) for target in ManyToManyTarget.objects.all(): source.targets.add(target) @@ -113,15 +109,15 @@ def test_many_to_many_retrieve(self): queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'targets': [self.targets[0].pk]}, - {'id': self.sources[1].pk, 'name': 'source-2', 'targets': [self.targets[0].pk, self.targets[1].pk]}, - {'id': self.sources[2].pk, 'name': 'source-3', 'targets': [self.targets[0].pk, self.targets[1].pk, self.targets[2].pk]} + {'id': 1, 'name': 'source-1', 'targets': [1]}, + {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, + {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]} ] with self.assertNumQueries(4): assert serializer.data == expected def test_many_to_many_retrieve_prefetch_related(self): - queryset = ManyToManySource.objects.order_by('pk').prefetch_related('targets') + queryset = ManyToManySource.objects.all().prefetch_related('targets') serializer = ManyToManySourceSerializer(queryset, many=True) with self.assertNumQueries(2): serializer.data @@ -130,16 +126,16 @@ def test_reverse_many_to_many_retrieve(self): queryset = ManyToManyTarget.objects.order_by('pk') serializer = ManyToManyTargetSerializer(queryset, many=True) expected = [ - {'id': self.targets[0].pk, 'name': 'target-1', 'sources': [self.sources[0].pk, self.sources[1].pk, self.sources[2].pk]}, - {'id': self.targets[1].pk, 'name': 'target-2', 'sources': [self.sources[1].pk, self.sources[2].pk]}, - {'id': self.targets[2].pk, 'name': 'target-3', 'sources': [self.sources[2].pk]} + {'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]}, + {'id': 2, 'name': 'target-2', 'sources': [2, 3]}, + {'id': 3, 'name': 'target-3', 'sources': [3]} ] with self.assertNumQueries(4): assert serializer.data == expected def test_many_to_many_update(self): - data = {'id': self.sources[0].pk, 'name': 'source-1', 'targets': [self.targets[0].pk, self.targets[1].pk, self.targets[2].pk]} - instance = ManyToManySource.objects.get(pk=self.sources[0].pk) + data = {'id': 1, 'name': 'source-1', 'targets': [1, 2, 3]} + instance = ManyToManySource.objects.get(pk=1) serializer = ManyToManySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() @@ -149,15 +145,15 @@ def test_many_to_many_update(self): queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'targets': [self.targets[0].pk, self.targets[1].pk, self.targets[2].pk]}, - {'id': self.sources[1].pk, 'name': 'source-2', 'targets': [self.targets[0].pk, self.targets[1].pk]}, - {'id': self.sources[2].pk, 'name': 'source-3', 'targets': [self.targets[0].pk, self.targets[1].pk, self.targets[2].pk]} + {'id': 1, 'name': 'source-1', 'targets': [1, 2, 3]}, + {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, + {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]} ] assert serializer.data == expected def test_reverse_many_to_many_update(self): - data = {'id': self.targets[0].pk, 'name': 'target-1', 'sources': [self.sources[0].pk]} - instance = ManyToManyTarget.objects.get(pk=self.targets[0].pk) + data = {'id': 1, 'name': 'target-1', 'sources': [1]} + instance = ManyToManyTarget.objects.get(pk=1) serializer = ManyToManyTargetSerializer(instance, data=data) assert serializer.is_valid() serializer.save() @@ -167,28 +163,28 @@ def test_reverse_many_to_many_update(self): queryset = ManyToManyTarget.objects.order_by('pk') serializer = ManyToManyTargetSerializer(queryset, many=True) expected = [ - {'id': self.targets[0].pk, 'name': 'target-1', 'sources': [self.sources[0].pk]}, - {'id': self.targets[1].pk, 'name': 'target-2', 'sources': [self.sources[1].pk, self.sources[2].pk]}, - {'id': self.targets[2].pk, 'name': 'target-3', 'sources': [self.sources[2].pk]} + {'id': 1, 'name': 'target-1', 'sources': [1]}, + {'id': 2, 'name': 'target-2', 'sources': [2, 3]}, + {'id': 3, 'name': 'target-3', 'sources': [3]} ] assert serializer.data == expected def test_many_to_many_create(self): - data = {'name': 'source-4', 'targets': [self.targets[0].pk, self.targets[2].pk]} + data = {'id': 4, 'name': 'source-4', 'targets': [1, 3]} serializer = ManyToManySourceSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == {'id': obj.pk, 'name': 'source-4', 'targets': [self.targets[0].pk, self.targets[2].pk]} + assert serializer.data == data assert obj.name == 'source-4' # Ensure source 4 is added, and everything else is as expected queryset = ManyToManySource.objects.order_by('pk') serializer = ManyToManySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'targets': [self.targets[0].pk]}, - {'id': self.sources[1].pk, 'name': 'source-2', 'targets': [self.targets[0].pk, self.targets[1].pk]}, - {'id': self.sources[2].pk, 'name': 'source-3', 'targets': [self.targets[0].pk, self.targets[1].pk, self.targets[2].pk]}, - {'id': obj.pk, 'name': 'source-4', 'targets': [self.targets[0].pk, self.targets[2].pk]}, + {'id': 1, 'name': 'source-1', 'targets': [1]}, + {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, + {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]}, + {'id': 4, 'name': 'source-4', 'targets': [1, 3]}, ] assert serializer.data == expected @@ -203,28 +199,28 @@ def test_many_to_many_unsaved(self): assert serializer.data == expected def test_reverse_many_to_many_create(self): - data = {'name': 'target-4', 'sources': [self.sources[0].pk, self.sources[2].pk]} + data = {'id': 4, 'name': 'target-4', 'sources': [1, 3]} serializer = ManyToManyTargetSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == {'id': obj.pk, 'name': 'target-4', 'sources': [self.sources[0].pk, self.sources[2].pk]} + assert serializer.data == data assert obj.name == 'target-4' # Ensure target 4 is added, and everything else is as expected queryset = ManyToManyTarget.objects.order_by('pk') serializer = ManyToManyTargetSerializer(queryset, many=True) expected = [ - {'id': self.targets[0].pk, 'name': 'target-1', 'sources': [self.sources[0].pk, self.sources[1].pk, self.sources[2].pk]}, - {'id': self.targets[1].pk, 'name': 'target-2', 'sources': [self.sources[1].pk, self.sources[2].pk]}, - {'id': self.targets[2].pk, 'name': 'target-3', 'sources': [self.sources[2].pk]}, - {'id': obj.pk, 'name': 'target-4', 'sources': [self.sources[0].pk, self.sources[2].pk]} + {'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]}, + {'id': 2, 'name': 'target-2', 'sources': [2, 3]}, + {'id': 3, 'name': 'target-3', 'sources': [3]}, + {'id': 4, 'name': 'target-4', 'sources': [1, 3]} ] assert serializer.data == expected def test_data_cannot_be_accessed_prior_to_is_valid(self): """Test that .data cannot be accessed prior to .is_valid for primary key serializers.""" serializer = ManyToManySourceSerializer( - data={'name': 'test-source', 'targets': [self.targets[0].pk]} + data={'name': 'test-source', 'targets': [1]} ) with pytest.raises(AssertionError): serializer.data @@ -232,23 +228,21 @@ def test_data_cannot_be_accessed_prior_to_is_valid(self): class PKForeignKeyTests(TestCase): def setUp(self): - self.target = ForeignKeyTarget(name='target-1') - self.target.save() - self.new_target = ForeignKeyTarget(name='target-2') - self.new_target.save() - self.sources = [] + target = ForeignKeyTarget(name='target-1') + target.save() + new_target = ForeignKeyTarget(name='target-2') + new_target.save() for idx in range(1, 4): - source = ForeignKeySource(name='source-%d' % idx, target=self.target) + source = ForeignKeySource(name='source-%d' % idx, target=target) source.save() - self.sources.append(source) def test_foreign_key_retrieve(self): queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.target.pk}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': self.target.pk} + {'id': 1, 'name': 'source-1', 'target': 1}, + {'id': 2, 'name': 'source-2', 'target': 1}, + {'id': 3, 'name': 'source-3', 'target': 1} ] with self.assertNumQueries(1): assert serializer.data == expected @@ -257,21 +251,21 @@ def test_reverse_foreign_key_retrieve(self): queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': self.target.pk, 'name': 'target-1', 'sources': [self.sources[0].pk, self.sources[1].pk, self.sources[2].pk]}, - {'id': self.new_target.pk, 'name': 'target-2', 'sources': []}, + {'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]}, + {'id': 2, 'name': 'target-2', 'sources': []}, ] with self.assertNumQueries(3): assert serializer.data == expected def test_reverse_foreign_key_retrieve_prefetch_related(self): - queryset = ForeignKeyTarget.objects.order_by('pk').prefetch_related('sources') + queryset = ForeignKeyTarget.objects.all().prefetch_related('sources') serializer = ForeignKeyTargetSerializer(queryset, many=True) with self.assertNumQueries(2): serializer.data def test_foreign_key_update(self): - data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.new_target.pk} - instance = ForeignKeySource.objects.get(pk=self.sources[0].pk) + data = {'id': 1, 'name': 'source-1', 'target': 2} + instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() @@ -281,22 +275,22 @@ def test_foreign_key_update(self): queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.new_target.pk}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': self.target.pk} + {'id': 1, 'name': 'source-1', 'target': 2}, + {'id': 2, 'name': 'source-2', 'target': 1}, + {'id': 3, 'name': 'source-3', 'target': 1} ] assert serializer.data == expected def test_foreign_key_update_incorrect_type(self): - data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'foo'} - instance = ForeignKeySource.objects.get(pk=self.sources[0].pk) + data = {'id': 1, 'name': 'source-1', 'target': 'foo'} + instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data) assert not serializer.is_valid() assert serializer.errors == {'target': ['Incorrect type. Expected pk value, received str.']} def test_reverse_foreign_key_update(self): - data = {'id': self.new_target.pk, 'name': 'target-2', 'sources': [self.sources[0].pk, self.sources[2].pk]} - instance = ForeignKeyTarget.objects.get(pk=self.new_target.pk) + data = {'id': 2, 'name': 'target-2', 'sources': [1, 3]} + instance = ForeignKeyTarget.objects.get(pk=2) serializer = ForeignKeyTargetSerializer(instance, data=data) assert serializer.is_valid() # We shouldn't have saved anything to the db yet since save @@ -304,8 +298,8 @@ def test_reverse_foreign_key_update(self): queryset = ForeignKeyTarget.objects.order_by('pk') new_serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': self.target.pk, 'name': 'target-1', 'sources': [self.sources[0].pk, self.sources[1].pk, self.sources[2].pk]}, - {'id': self.new_target.pk, 'name': 'target-2', 'sources': []}, + {'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]}, + {'id': 2, 'name': 'target-2', 'sources': []}, ] assert new_serializer.data == expected @@ -316,51 +310,51 @@ def test_reverse_foreign_key_update(self): queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': self.target.pk, 'name': 'target-1', 'sources': [self.sources[1].pk]}, - {'id': self.new_target.pk, 'name': 'target-2', 'sources': [self.sources[0].pk, self.sources[2].pk]}, + {'id': 1, 'name': 'target-1', 'sources': [2]}, + {'id': 2, 'name': 'target-2', 'sources': [1, 3]}, ] assert serializer.data == expected def test_foreign_key_create(self): - data = {'name': 'source-4', 'target': self.new_target.pk} + data = {'id': 4, 'name': 'source-4', 'target': 2} serializer = ForeignKeySourceSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == {'id': obj.pk, 'name': 'source-4', 'target': self.new_target.pk} + assert serializer.data == data assert obj.name == 'source-4' # Ensure source 4 is added, and everything else is as expected queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.target.pk}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': self.target.pk}, - {'id': obj.pk, 'name': 'source-4', 'target': self.new_target.pk}, + {'id': 1, 'name': 'source-1', 'target': 1}, + {'id': 2, 'name': 'source-2', 'target': 1}, + {'id': 3, 'name': 'source-3', 'target': 1}, + {'id': 4, 'name': 'source-4', 'target': 2}, ] assert serializer.data == expected def test_reverse_foreign_key_create(self): - data = {'name': 'target-3', 'sources': [self.sources[0].pk, self.sources[2].pk]} + data = {'id': 3, 'name': 'target-3', 'sources': [1, 3]} serializer = ForeignKeyTargetSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == {'id': obj.pk, 'name': 'target-3', 'sources': [self.sources[0].pk, self.sources[2].pk]} + assert serializer.data == data assert obj.name == 'target-3' # Ensure target 3 is added, and everything else is as expected queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': self.target.pk, 'name': 'target-1', 'sources': [self.sources[1].pk]}, - {'id': self.new_target.pk, 'name': 'target-2', 'sources': []}, - {'id': obj.pk, 'name': 'target-3', 'sources': [self.sources[0].pk, self.sources[2].pk]}, + {'id': 1, 'name': 'target-1', 'sources': [2]}, + {'id': 2, 'name': 'target-2', 'sources': []}, + {'id': 3, 'name': 'target-3', 'sources': [1, 3]}, ] assert serializer.data == expected def test_foreign_key_update_with_invalid_null(self): - data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': None} - instance = ForeignKeySource.objects.get(pk=self.sources[0].pk) + data = {'id': 1, 'name': 'source-1', 'target': None} + instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data) assert not serializer.is_valid() assert serializer.errors == {'target': ['This field may not be null.']} @@ -425,15 +419,15 @@ class PKRelationTests(TestCase): def setUp(self): self.target = ForeignKeyTarget.objects.create(name='target-1') - self.source1 = ForeignKeySource.objects.create(name='source-1', target=self.target) - self.source2 = ForeignKeySource.objects.create(name='source-2', target=self.target) + ForeignKeySource.objects.create(name='source-1', target=self.target) + ForeignKeySource.objects.create(name='source-2', target=self.target) def test_relation_field_callable_source(self): serializer = ForeignKeyTargetCallableSourceSerializer(self.target) expected = { - 'id': self.target.pk, + 'id': 1, 'name': 'target-1', - 'first_source': self.source1.pk, + 'first_source': 1, } with self.assertNumQueries(1): self.assertEqual(serializer.data, expected) @@ -441,9 +435,9 @@ def test_relation_field_callable_source(self): def test_relation_field_property_source(self): serializer = ForeignKeyTargetPropertySourceSerializer(self.target) expected = { - 'id': self.target.pk, + 'id': 1, 'name': 'target-1', - 'first_source': self.source1.pk, + 'first_source': 1, } with self.assertNumQueries(1): self.assertEqual(serializer.data, expected) @@ -451,43 +445,40 @@ def test_relation_field_property_source(self): class PKNullableForeignKeyTests(TestCase): def setUp(self): - self.target = ForeignKeyTarget(name='target-1') - self.target.save() - self.sources = [] + target = ForeignKeyTarget(name='target-1') + target.save() for idx in range(1, 4): - target = self.target if idx == 3: target = None source = NullableForeignKeySource(name='source-%d' % idx, target=target) source.save() - self.sources.append(source) def test_foreign_key_retrieve_with_null(self): queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.target.pk}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': None}, + {'id': 1, 'name': 'source-1', 'target': 1}, + {'id': 2, 'name': 'source-2', 'target': 1}, + {'id': 3, 'name': 'source-3', 'target': None}, ] assert serializer.data == expected def test_foreign_key_create_with_valid_null(self): - data = {'name': 'source-4', 'target': None} + data = {'id': 4, 'name': 'source-4', 'target': None} serializer = NullableForeignKeySourceSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - assert serializer.data == {'id': obj.pk, 'name': 'source-4', 'target': None} + assert serializer.data == data assert obj.name == 'source-4' # Ensure source 4 is created, and everything else is as expected queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.target.pk}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': None}, - {'id': obj.pk, 'name': 'source-4', 'target': None} + {'id': 1, 'name': 'source-1', 'target': 1}, + {'id': 2, 'name': 'source-2', 'target': 1}, + {'id': 3, 'name': 'source-3', 'target': None}, + {'id': 4, 'name': 'source-4', 'target': None} ] assert serializer.data == expected @@ -496,11 +487,11 @@ def test_foreign_key_create_with_valid_emptystring(self): The emptystring should be interpreted as null in the context of relationships. """ - data = {'name': 'source-4', 'target': ''} + data = {'id': 4, 'name': 'source-4', 'target': ''} + expected_data = {'id': 4, 'name': 'source-4', 'target': None} serializer = NullableForeignKeySourceSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - expected_data = {'id': obj.pk, 'name': 'source-4', 'target': None} assert serializer.data == expected_data assert obj.name == 'source-4' @@ -508,16 +499,16 @@ def test_foreign_key_create_with_valid_emptystring(self): queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': self.target.pk}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': None}, - {'id': obj.pk, 'name': 'source-4', 'target': None} + {'id': 1, 'name': 'source-1', 'target': 1}, + {'id': 2, 'name': 'source-2', 'target': 1}, + {'id': 3, 'name': 'source-3', 'target': None}, + {'id': 4, 'name': 'source-4', 'target': None} ] assert serializer.data == expected def test_foreign_key_update_with_valid_null(self): - data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': None} - instance = NullableForeignKeySource.objects.get(pk=self.sources[0].pk) + data = {'id': 1, 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=1) serializer = NullableForeignKeySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() @@ -527,9 +518,9 @@ def test_foreign_key_update_with_valid_null(self): queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': None}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': None} + {'id': 1, 'name': 'source-1', 'target': None}, + {'id': 2, 'name': 'source-2', 'target': 1}, + {'id': 3, 'name': 'source-3', 'target': None} ] assert serializer.data == expected @@ -538,9 +529,9 @@ def test_foreign_key_update_with_valid_emptystring(self): The emptystring should be interpreted as null in the context of relationships. """ - data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': ''} - expected_data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': None} - instance = NullableForeignKeySource.objects.get(pk=self.sources[0].pk) + data = {'id': 1, 'name': 'source-1', 'target': ''} + expected_data = {'id': 1, 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=1) serializer = NullableForeignKeySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() @@ -550,9 +541,9 @@ def test_foreign_key_update_with_valid_emptystring(self): queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': None}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': self.target.pk}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': None} + {'id': 1, 'name': 'source-1', 'target': None}, + {'id': 2, 'name': 'source-2', 'target': 1}, + {'id': 3, 'name': 'source-3', 'target': None} ] assert serializer.data == expected @@ -570,19 +561,19 @@ def test_nullable_uuid_foreign_key_is_valid_when_none(self): class PKNullableOneToOneTests(TestCase): def setUp(self): - self.target1 = OneToOneTarget(name='target-1') - self.target1.save() - self.target2 = OneToOneTarget(name='target-2') - self.target2.save() - self.source = NullableOneToOneSource(name='source-1', target=self.target2) - self.source.save() + target = OneToOneTarget(name='target-1') + target.save() + new_target = OneToOneTarget(name='target-2') + new_target.save() + source = NullableOneToOneSource(name='source-1', target=new_target) + source.save() def test_reverse_foreign_key_retrieve_with_null(self): queryset = OneToOneTarget.objects.order_by('pk') serializer = NullableOneToOneTargetSerializer(queryset, many=True) expected = [ - {'id': self.target1.pk, 'name': 'target-1', 'nullable_source': None}, - {'id': self.target2.pk, 'name': 'target-2', 'nullable_source': self.source.pk}, + {'id': 1, 'name': 'target-1', 'nullable_source': None}, + {'id': 2, 'name': 'target-2', 'nullable_source': 1}, ] assert serializer.data == expected diff --git a/tests/test_relations_slug.py b/tests/test_relations_slug.py index b3ecc6e268..03f60d46b5 100644 --- a/tests/test_relations_slug.py +++ b/tests/test_relations_slug.py @@ -44,85 +44,83 @@ class Meta: # TODO: M2M Tests, FKTests (Non-nullable), One2One class SlugForeignKeyTests(TestCase): def setUp(self): - self.target = ForeignKeyTarget(name='target-1') - self.target.save() - self.new_target = ForeignKeyTarget(name='target-2') - self.new_target.save() - self.sources = [] + target = ForeignKeyTarget(name='target-1') + target.save() + new_target = ForeignKeyTarget(name='target-2') + new_target.save() for idx in range(1, 4): - source = ForeignKeySource(name='source-%d' % idx, target=self.target) + source = ForeignKeySource(name='source-%d' % idx, target=target) source.save() - self.sources.append(source) def test_foreign_key_retrieve(self): - queryset = ForeignKeySource.objects.all().order_by('pk') + queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-1'}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': 'target-1'} + {'id': 1, 'name': 'source-1', 'target': 'target-1'}, + {'id': 2, 'name': 'source-2', 'target': 'target-1'}, + {'id': 3, 'name': 'source-3', 'target': 'target-1'} ] with self.assertNumQueries(4): assert serializer.data == expected def test_foreign_key_retrieve_select_related(self): - queryset = ForeignKeySource.objects.all().order_by('pk').select_related('target') + queryset = ForeignKeySource.objects.order_by('pk').select_related('target') serializer = ForeignKeySourceSerializer(queryset, many=True) with self.assertNumQueries(1): serializer.data def test_reverse_foreign_key_retrieve(self): - queryset = ForeignKeyTarget.objects.all().order_by('pk') + queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': self.target.pk, 'name': 'target-1', 'sources': ['source-1', 'source-2', 'source-3']}, - {'id': self.new_target.pk, 'name': 'target-2', 'sources': []}, + {'id': 1, 'name': 'target-1', 'sources': ['source-1', 'source-2', 'source-3']}, + {'id': 2, 'name': 'target-2', 'sources': []}, ] assert serializer.data == expected def test_reverse_foreign_key_retrieve_prefetch_related(self): - queryset = ForeignKeyTarget.objects.all().order_by('pk').prefetch_related('sources') + queryset = ForeignKeyTarget.objects.order_by('pk').prefetch_related('sources') serializer = ForeignKeyTargetSerializer(queryset, many=True) with self.assertNumQueries(2): serializer.data def test_foreign_key_update(self): - data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-2'} - instance = ForeignKeySource.objects.get(pk=self.sources[0].pk) + data = {'id': 1, 'name': 'source-1', 'target': 'target-2'} + instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() assert serializer.data == data # Ensure source 1 is updated, and everything else is as expected - queryset = ForeignKeySource.objects.all().order_by('pk') + queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-2'}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': 'target-1'} + {'id': 1, 'name': 'source-1', 'target': 'target-2'}, + {'id': 2, 'name': 'source-2', 'target': 'target-1'}, + {'id': 3, 'name': 'source-3', 'target': 'target-1'} ] assert serializer.data == expected def test_foreign_key_update_incorrect_type(self): - data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': 123} - instance = ForeignKeySource.objects.get(pk=self.sources[0].pk) + data = {'id': 1, 'name': 'source-1', 'target': 123} + instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data) assert not serializer.is_valid() assert serializer.errors == {'target': ['Object with name=123 does not exist.']} def test_reverse_foreign_key_update(self): - data = {'id': self.new_target.pk, 'name': 'target-2', 'sources': ['source-1', 'source-3']} - instance = ForeignKeyTarget.objects.get(pk=self.new_target.pk) + data = {'id': 2, 'name': 'target-2', 'sources': ['source-1', 'source-3']} + instance = ForeignKeyTarget.objects.get(pk=2) serializer = ForeignKeyTargetSerializer(instance, data=data) assert serializer.is_valid() # We shouldn't have saved anything to the db yet since save # hasn't been called. - queryset = ForeignKeyTarget.objects.all().order_by('pk') + queryset = ForeignKeyTarget.objects.order_by('pk') new_serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': self.target.pk, 'name': 'target-1', 'sources': ['source-1', 'source-2', 'source-3']}, - {'id': self.new_target.pk, 'name': 'target-2', 'sources': []}, + {'id': 1, 'name': 'target-1', 'sources': ['source-1', 'source-2', 'source-3']}, + {'id': 2, 'name': 'target-2', 'sources': []}, ] assert new_serializer.data == expected @@ -130,57 +128,55 @@ def test_reverse_foreign_key_update(self): assert serializer.data == data # Ensure target 2 is update, and everything else is as expected - queryset = ForeignKeyTarget.objects.all().order_by('pk') + queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': self.target.pk, 'name': 'target-1', 'sources': ['source-2']}, - {'id': self.new_target.pk, 'name': 'target-2', 'sources': ['source-1', 'source-3']}, + {'id': 1, 'name': 'target-1', 'sources': ['source-2']}, + {'id': 2, 'name': 'target-2', 'sources': ['source-1', 'source-3']}, ] assert serializer.data == expected def test_foreign_key_create(self): - data = {'name': 'source-4', 'target': 'target-2'} + data = {'id': 4, 'name': 'source-4', 'target': 'target-2'} serializer = ForeignKeySourceSerializer(data=data) serializer.is_valid() assert serializer.is_valid() obj = serializer.save() - expected_data = {'id': obj.pk, 'name': 'source-4', 'target': 'target-2'} - assert serializer.data == expected_data + assert serializer.data == data assert obj.name == 'source-4' # Ensure source 4 is added, and everything else is as expected - queryset = ForeignKeySource.objects.all().order_by('pk') + queryset = ForeignKeySource.objects.order_by('pk') serializer = ForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-1'}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': 'target-1'}, - {'id': obj.pk, 'name': 'source-4', 'target': 'target-2'}, + {'id': 1, 'name': 'source-1', 'target': 'target-1'}, + {'id': 2, 'name': 'source-2', 'target': 'target-1'}, + {'id': 3, 'name': 'source-3', 'target': 'target-1'}, + {'id': 4, 'name': 'source-4', 'target': 'target-2'}, ] assert serializer.data == expected def test_reverse_foreign_key_create(self): - data = {'name': 'target-3', 'sources': ['source-1', 'source-3']} + data = {'id': 3, 'name': 'target-3', 'sources': ['source-1', 'source-3']} serializer = ForeignKeyTargetSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - expected_data = {'id': obj.pk, 'name': 'target-3', 'sources': ['source-1', 'source-3']} - assert serializer.data == expected_data + assert serializer.data == data assert obj.name == 'target-3' # Ensure target 3 is added, and everything else is as expected - queryset = ForeignKeyTarget.objects.all().order_by('pk') + queryset = ForeignKeyTarget.objects.order_by('pk') serializer = ForeignKeyTargetSerializer(queryset, many=True) expected = [ - {'id': self.target.pk, 'name': 'target-1', 'sources': ['source-2']}, - {'id': self.new_target.pk, 'name': 'target-2', 'sources': []}, - {'id': obj.pk, 'name': 'target-3', 'sources': ['source-1', 'source-3']}, + {'id': 1, 'name': 'target-1', 'sources': ['source-2']}, + {'id': 2, 'name': 'target-2', 'sources': []}, + {'id': 3, 'name': 'target-3', 'sources': ['source-1', 'source-3']}, ] assert serializer.data == expected def test_foreign_key_update_with_invalid_null(self): - data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': None} - instance = ForeignKeySource.objects.get(pk=self.sources[0].pk) + data = {'id': 1, 'name': 'source-1', 'target': None} + instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data) assert not serializer.is_valid() assert serializer.errors == {'target': ['This field may not be null.']} @@ -188,44 +184,40 @@ def test_foreign_key_update_with_invalid_null(self): class SlugNullableForeignKeyTests(TestCase): def setUp(self): - self.target = ForeignKeyTarget(name='target-1') - self.target.save() - self.sources = [] + target = ForeignKeyTarget(name='target-1') + target.save() for idx in range(1, 4): - target = self.target if idx == 3: target = None source = NullableForeignKeySource(name='source-%d' % idx, target=target) source.save() - self.sources.append(source) def test_foreign_key_retrieve_with_null(self): - queryset = NullableForeignKeySource.objects.all().order_by('pk') + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-1'}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': None}, + {'id': 1, 'name': 'source-1', 'target': 'target-1'}, + {'id': 2, 'name': 'source-2', 'target': 'target-1'}, + {'id': 3, 'name': 'source-3', 'target': None}, ] assert serializer.data == expected def test_foreign_key_create_with_valid_null(self): - data = {'name': 'source-4', 'target': None} + data = {'id': 4, 'name': 'source-4', 'target': None} serializer = NullableForeignKeySourceSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - expected_data = {'id': obj.pk, 'name': 'source-4', 'target': None} - assert serializer.data == expected_data + assert serializer.data == data assert obj.name == 'source-4' # Ensure source 4 is created, and everything else is as expected - queryset = NullableForeignKeySource.objects.all().order_by('pk') + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-1'}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': None}, - {'id': obj.pk, 'name': 'source-4', 'target': None} + {'id': 1, 'name': 'source-1', 'target': 'target-1'}, + {'id': 2, 'name': 'source-2', 'target': 'target-1'}, + {'id': 3, 'name': 'source-3', 'target': None}, + {'id': 4, 'name': 'source-4', 'target': None} ] assert serializer.data == expected @@ -234,40 +226,40 @@ def test_foreign_key_create_with_valid_emptystring(self): The emptystring should be interpreted as null in the context of relationships. """ - data = {'name': 'source-4', 'target': ''} + data = {'id': 4, 'name': 'source-4', 'target': ''} + expected_data = {'id': 4, 'name': 'source-4', 'target': None} serializer = NullableForeignKeySourceSerializer(data=data) assert serializer.is_valid() obj = serializer.save() - expected_data = {'id': obj.pk, 'name': 'source-4', 'target': None} assert serializer.data == expected_data assert obj.name == 'source-4' # Ensure source 4 is created, and everything else is as expected - queryset = NullableForeignKeySource.objects.all().order_by('pk') + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': 'target-1'}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': None}, - {'id': obj.pk, 'name': 'source-4', 'target': None} + {'id': 1, 'name': 'source-1', 'target': 'target-1'}, + {'id': 2, 'name': 'source-2', 'target': 'target-1'}, + {'id': 3, 'name': 'source-3', 'target': None}, + {'id': 4, 'name': 'source-4', 'target': None} ] assert serializer.data == expected def test_foreign_key_update_with_valid_null(self): - data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': None} - instance = NullableForeignKeySource.objects.get(pk=self.sources[0].pk) + data = {'id': 1, 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=1) serializer = NullableForeignKeySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() assert serializer.data == data # Ensure source 1 is updated, and everything else is as expected - queryset = NullableForeignKeySource.objects.all().order_by('pk') + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': None}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': None} + {'id': 1, 'name': 'source-1', 'target': None}, + {'id': 2, 'name': 'source-2', 'target': 'target-1'}, + {'id': 3, 'name': 'source-3', 'target': None} ] assert serializer.data == expected @@ -276,20 +268,20 @@ def test_foreign_key_update_with_valid_emptystring(self): The emptystring should be interpreted as null in the context of relationships. """ - data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': ''} - expected_data = {'id': self.sources[0].pk, 'name': 'source-1', 'target': None} - instance = NullableForeignKeySource.objects.get(pk=self.sources[0].pk) + data = {'id': 1, 'name': 'source-1', 'target': ''} + expected_data = {'id': 1, 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=1) serializer = NullableForeignKeySourceSerializer(instance, data=data) assert serializer.is_valid() serializer.save() assert serializer.data == expected_data # Ensure source 1 is updated, and everything else is as expected - queryset = NullableForeignKeySource.objects.all().order_by('pk') + queryset = NullableForeignKeySource.objects.order_by('pk') serializer = NullableForeignKeySourceSerializer(queryset, many=True) expected = [ - {'id': self.sources[0].pk, 'name': 'source-1', 'target': None}, - {'id': self.sources[1].pk, 'name': 'source-2', 'target': 'target-1'}, - {'id': self.sources[2].pk, 'name': 'source-3', 'target': None} + {'id': 1, 'name': 'source-1', 'target': None}, + {'id': 2, 'name': 'source-2', 'target': 'target-1'}, + {'id': 3, 'name': 'source-3', 'target': None} ] assert serializer.data == expected diff --git a/tests/test_validators.py b/tests/test_validators.py index aaef20f4ce..f58cd50a01 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -662,13 +662,13 @@ def setUp(self): global_id=1, fancy_conditions=1 ) - self.instance2 = UniqueConstraintModel.objects.create( + UniqueConstraintModel.objects.create( race_name='example', position=2, global_id=2, fancy_conditions=1 ) - self.instance3 = UniqueConstraintModel.objects.create( + UniqueConstraintModel.objects.create( race_name='other', position=1, global_id=3, @@ -746,7 +746,6 @@ def test_single_field_uniq_validators(self): # SQLite does not because it has no fixed integer range. has_int_range = connection.ops.integer_field_range('IntegerField')[0] is not None extra_validators_qty = 2 if has_int_range else 0 - serializer = UniqueConstraintSerializer() assert len(serializer.validators) == 2 validators = serializer.fields['global_id'].validators @@ -756,7 +755,7 @@ def test_single_field_uniq_validators(self): validators = serializer.fields['fancy_conditions'].validators assert len(validators) == 2 + extra_validators_qty ids_in_qs = {frozenset(v.queryset.values_list('id', flat=True)) for v in validators if hasattr(v, "queryset")} - assert ids_in_qs == {frozenset([self.instance.pk]), frozenset([self.instance3.pk])} + assert ids_in_qs == {frozenset([1]), frozenset([3])} def test_nullable_unique_constraint_fields_are_not_required(self): serializer = UniqueConstraintNullableSerializer(data={'title': 'Bob'}) From 8b41cc5c41c50bfff90e4fd82fd8154e29ef520c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asif=20Saif=20Uddin=20=7B=22Auvi=22=3A=22=E0=A6=85?= =?UTF-8?q?=E0=A6=AD=E0=A6=BF=22=7D?= Date: Wed, 29 Apr 2026 13:20:03 +0600 Subject: [PATCH 09/12] Update tox.ini Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0bb3c6aa1d..63e075c1c1 100644 --- a/tox.ini +++ b/tox.ini @@ -32,7 +32,6 @@ dependency_groups = dependency_groups = test deps = -pass_env = [testenv:dist] commands = python -W error::DeprecationWarning -W error::PendingDeprecationWarning runtests.py --no-pkgroot --staticfiles {posargs} From 2bcb0632a3b6ff83a20b29aff304085176d5313b Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Wed, 29 Apr 2026 21:07:13 +0100 Subject: [PATCH 10/12] Revert "Update tox.ini" This reverts commit 8b41cc5c41c50bfff90e4fd82fd8154e29ef520c. --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 63e075c1c1..0bb3c6aa1d 100644 --- a/tox.ini +++ b/tox.ini @@ -32,6 +32,7 @@ dependency_groups = dependency_groups = test deps = +pass_env = [testenv:dist] commands = python -W error::DeprecationWarning -W error::PendingDeprecationWarning runtests.py --no-pkgroot --staticfiles {posargs} From 7fbbf9b9c19fedc90ff0603f761f06c64a6ce000 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Wed, 29 Apr 2026 21:08:36 +0100 Subject: [PATCH 11/12] Add comment to explain why pass_env is empty --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 0bb3c6aa1d..3dcdbf92ed 100644 --- a/tox.ini +++ b/tox.ini @@ -32,6 +32,7 @@ dependency_groups = dependency_groups = test deps = +# Unset passing the DATABASE_URL env var to run on SQLite pass_env = [testenv:dist] From d8df0f10c02ea2478ece94dbe70ead1e04f9ba0c Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Wed, 29 Apr 2026 21:17:12 +0100 Subject: [PATCH 12/12] Refactor disconnect + connect into a context manager --- rest_framework/test.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/rest_framework/test.py b/rest_framework/test.py index 9f6aab22bf..675f789068 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -1,6 +1,7 @@ # Note that we import as `DjangoRequestFactory` and `DjangoClient` in order # to make it harder for the user to import the wrong thing without realizing. import io +from contextlib import contextmanager from importlib import import_module from django.conf import settings @@ -24,6 +25,21 @@ def force_authenticate(request, user=None, token=None): request._force_auth_token = token +@contextmanager +def _keep_connections_open(): + """ + Prevent Django from closing the database connection while a request + is dispatched, matching the behavior of Django's ClientHandler. + """ + request_started.disconnect(close_old_connections) + request_finished.disconnect(close_old_connections) + try: + yield + finally: + request_started.connect(close_old_connections) + request_finished.connect(close_old_connections) + + if requests is not None: class HeaderDict(requests.packages.urllib3._collections.HTTPHeaderDict): def get_all(self, key, default): @@ -91,17 +107,9 @@ 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) - request_started.disconnect(close_old_connections) - request_finished.disconnect(close_old_connections) - try: + with _keep_connections_open(): 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))