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", 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 f797a01eb7..8bae8d30fe 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) @@ -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..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,9 +71,25 @@ 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() @@ -81,7 +99,7 @@ def setUp(self): source.targets.add(target) 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/']}, @@ -92,7 +110,7 @@ def test_relative_hyperlinks(self): 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/']}, @@ -109,7 +127,7 @@ 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/']}, @@ -128,7 +146,7 @@ def test_many_to_many_update(self): 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/']}, @@ -145,7 +163,7 @@ def test_reverse_many_to_many_update(self): 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/']}, @@ -164,7 +182,7 @@ def test_many_to_many_create(self): 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/']}, @@ -183,7 +201,7 @@ def test_reverse_many_to_many_create(self): 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/']}, @@ -206,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') @@ -215,7 +234,7 @@ def setUp(self): source.save() 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/'}, @@ -226,7 +245,7 @@ def test_foreign_key_retrieve(self): 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/']}, @@ -244,7 +263,7 @@ def test_foreign_key_update(self): 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/'}, @@ -267,7 +286,7 @@ def test_reverse_foreign_key_update(self): 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/']}, @@ -279,7 +298,7 @@ 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/']}, @@ -296,7 +315,7 @@ def test_foreign_key_create(self): 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/'}, @@ -315,7 +334,7 @@ def test_reverse_foreign_key_create(self): 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/']}, @@ -335,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): @@ -344,7 +364,7 @@ def setUp(self): source.save() 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/'}, @@ -362,7 +382,7 @@ def test_foreign_key_create_with_valid_null(self): 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/'}, @@ -386,7 +406,7 @@ def test_foreign_key_create_with_valid_emptystring(self): 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/'}, @@ -405,7 +425,7 @@ def test_foreign_key_update_with_valid_null(self): 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}, @@ -428,7 +448,7 @@ def test_foreign_key_update_with_valid_emptystring(self): 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}, @@ -441,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') @@ -449,7 +470,7 @@ def setUp(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, context={'request': request}) expected = [ {'url': 'http://testserver/onetoonetarget/1/', 'name': 'target-1', 'nullable_source': 'http://testserver/nullableonetoonesource/1/'}, 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..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 @@ -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, @@ -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 @@ -753,7 +756,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'}) diff --git a/tox.ini b/tox.ini index 00e5bd57b8..0bb3c6aa1d 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 @@ -30,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}