From a91dca178dc9681a0411a343b960a6f9a9dd8011 Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Fri, 8 Mar 2013 17:01:43 +0000 Subject: Correcy typo. --- rest_framework/tests/generics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/tests') diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py index f7093401..5f206769 100644 --- a/rest_framework/tests/generics.py +++ b/rest_framework/tests/generics.py @@ -263,7 +263,7 @@ class TestInstanceView(TestCase): at the requested url if it doesn't exist. """ content = {'text': 'foobar'} - # pk fields can not be created on demand, only the database can set th pk for a new object + # pk fields can not be created on demand, only the database can set the pk for a new object request = factory.put('/5', json.dumps(content), content_type='application/json') response = self.view(request, pk=5).render() -- cgit v1.2.3 From 332c99748f2e0ebc6490b8e7379d8a4b48ba8ee2 Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Fri, 8 Mar 2013 17:36:43 +0000 Subject: Add some simple numQueries tests. --- rest_framework/tests/generics.py | 48 ++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 16 deletions(-) (limited to 'rest_framework/tests') diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py index 5f206769..1837898b 100644 --- a/rest_framework/tests/generics.py +++ b/rest_framework/tests/generics.py @@ -60,7 +60,8 @@ class TestRootView(TestCase): GET requests to ListCreateAPIView should return list of objects. """ request = factory.get('/') - response = self.view(request).render() + with self.assertNumQueries(1): + response = self.view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, self.data) @@ -71,7 +72,8 @@ class TestRootView(TestCase): content = {'text': 'foobar'} request = factory.post('/', json.dumps(content), content_type='application/json') - response = self.view(request).render() + with self.assertNumQueries(1): + response = self.view(request).render() self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data, {'id': 4, 'text': 'foobar'}) created = self.objects.get(id=4) @@ -84,7 +86,8 @@ class TestRootView(TestCase): content = {'text': 'foobar'} request = factory.put('/', json.dumps(content), content_type='application/json') - response = self.view(request).render() + with self.assertNumQueries(0): + response = self.view(request).render() self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) self.assertEqual(response.data, {"detail": "Method 'PUT' not allowed."}) @@ -93,7 +96,8 @@ class TestRootView(TestCase): DELETE requests to ListCreateAPIView should not be allowed """ request = factory.delete('/') - response = self.view(request).render() + with self.assertNumQueries(0): + response = self.view(request).render() self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) self.assertEqual(response.data, {"detail": "Method 'DELETE' not allowed."}) @@ -102,7 +106,8 @@ class TestRootView(TestCase): OPTIONS requests to ListCreateAPIView should return metadata """ request = factory.options('/') - response = self.view(request).render() + with self.assertNumQueries(0): + response = self.view(request).render() expected = { 'parses': [ 'application/json', @@ -126,7 +131,8 @@ class TestRootView(TestCase): content = {'id': 999, 'text': 'foobar'} request = factory.post('/', json.dumps(content), content_type='application/json') - response = self.view(request).render() + with self.assertNumQueries(1): + response = self.view(request).render() self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data, {'id': 4, 'text': 'foobar'}) created = self.objects.get(id=4) @@ -154,7 +160,8 @@ class TestInstanceView(TestCase): GET requests to RetrieveUpdateDestroyAPIView should return a single object. """ request = factory.get('/1') - response = self.view(request, pk=1).render() + with self.assertNumQueries(1): + response = self.view(request, pk=1).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, self.data[0]) @@ -165,7 +172,8 @@ class TestInstanceView(TestCase): content = {'text': 'foobar'} request = factory.post('/', json.dumps(content), content_type='application/json') - response = self.view(request).render() + with self.assertNumQueries(0): + response = self.view(request).render() self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) self.assertEqual(response.data, {"detail": "Method 'POST' not allowed."}) @@ -176,7 +184,8 @@ class TestInstanceView(TestCase): content = {'text': 'foobar'} request = factory.put('/1', json.dumps(content), content_type='application/json') - response = self.view(request, pk='1').render() + with self.assertNumQueries(3): + response = self.view(request, pk='1').render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, {'id': 1, 'text': 'foobar'}) updated = self.objects.get(id=1) @@ -190,7 +199,8 @@ class TestInstanceView(TestCase): request = factory.patch('/1', json.dumps(content), content_type='application/json') - response = self.view(request, pk=1).render() + with self.assertNumQueries(3): + response = self.view(request, pk=1).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, {'id': 1, 'text': 'foobar'}) updated = self.objects.get(id=1) @@ -201,7 +211,8 @@ class TestInstanceView(TestCase): DELETE requests to RetrieveUpdateDestroyAPIView should delete an object. """ request = factory.delete('/1') - response = self.view(request, pk=1).render() + with self.assertNumQueries(2): + response = self.view(request, pk=1).render() self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.content, six.b('')) ids = [obj.id for obj in self.objects.all()] @@ -212,7 +223,8 @@ class TestInstanceView(TestCase): OPTIONS requests to RetrieveUpdateDestroyAPIView should return metadata """ request = factory.options('/') - response = self.view(request).render() + with self.assertNumQueries(0): + response = self.view(request).render() expected = { 'parses': [ 'application/json', @@ -236,7 +248,8 @@ class TestInstanceView(TestCase): content = {'id': 999, 'text': 'foobar'} request = factory.put('/1', json.dumps(content), content_type='application/json') - response = self.view(request, pk=1).render() + with self.assertNumQueries(3): + response = self.view(request, pk=1).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, {'id': 1, 'text': 'foobar'}) updated = self.objects.get(id=1) @@ -251,7 +264,8 @@ class TestInstanceView(TestCase): content = {'text': 'foobar'} request = factory.put('/1', json.dumps(content), content_type='application/json') - response = self.view(request, pk=1).render() + with self.assertNumQueries(4): + response = self.view(request, pk=1).render() self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data, {'id': 1, 'text': 'foobar'}) updated = self.objects.get(id=1) @@ -266,7 +280,8 @@ class TestInstanceView(TestCase): # pk fields can not be created on demand, only the database can set the pk for a new object request = factory.put('/5', json.dumps(content), content_type='application/json') - response = self.view(request, pk=5).render() + with self.assertNumQueries(4): + response = self.view(request, pk=5).render() self.assertEqual(response.status_code, status.HTTP_201_CREATED) new_obj = self.objects.get(pk=5) self.assertEqual(new_obj.text, 'foobar') @@ -279,7 +294,8 @@ class TestInstanceView(TestCase): content = {'text': 'foobar'} request = factory.put('/test_slug', json.dumps(content), content_type='application/json') - response = self.slug_based_view(request, slug='test_slug').render() + with self.assertNumQueries(2): + response = self.slug_based_view(request, slug='test_slug').render() self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data, {'slug': 'test_slug', 'text': 'foobar'}) new_obj = SlugBasedModel.objects.get(slug='test_slug') -- cgit v1.2.3 From e7e470739fc4d2694d1c0e2dbf3f6465b44ad069 Mon Sep 17 00:00:00 2001 From: Mjumbe Wawatu Ukweli Date: Mon, 11 Mar 2013 03:23:44 -0400 Subject: Use parent's primary key when model is derived via multitable inheritance --- rest_framework/tests/models.py | 14 +++++++++++++ rest_framework/tests/serializer.py | 42 +++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) (limited to 'rest_framework/tests') diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index f2117538..fcfe5a0c 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -166,3 +166,17 @@ class NullableOneToOneSource(RESTFrameworkModel): name = models.CharField(max_length=100) target = models.OneToOneField(OneToOneTarget, null=True, blank=True, related_name='nullable_source') + + +# Inherited +class ParentModel(RESTFrameworkModel): + name1 = models.CharField(max_length=100) + + +class ChildModel(ParentModel): + name2 = models.CharField(max_length=100) + + +class AssociatedModel(RESTFrameworkModel): + ref = models.OneToOneField(ParentModel, primary_key=True) + name = models.CharField(max_length=100) diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index beb372c2..93909b65 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -4,7 +4,8 @@ from django.test import TestCase from rest_framework import serializers from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel, BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel, - ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo) + ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, ParentModel, ChildModel, + AssociatedModel) import datetime import pickle @@ -96,6 +97,16 @@ class BrokenModelSerializer(serializers.ModelSerializer): fields = ['some_field'] +class DerivedModelSerializer(serializers.ModelSerializer): + class Meta: + model = ChildModel + + +class AssociatedModelSerializer(serializers.ModelSerializer): + class Meta: + model = AssociatedModel + + class BasicTests(TestCase): def setUp(self): self.comment = Comment( @@ -170,6 +181,27 @@ class BasicTests(TestCase): self.assertEqual(set(serializer.data.keys()), set(['name', 'age', 'info'])) + def test_multitable_inherited_model_fields_as_expected(self): + """ + Assert that the parent pointer field is not included in the fields + serialized fields + """ + child = ChildModel(name1='parent name', name2='child name') + serializer = DerivedModelSerializer(child) + self.assertEqual(set(serializer.data.keys()), + set(['name1', 'name2', 'id'])) + + def test_onetoone_primary_key_model_fields_as_expected(self): + """ + Assert that a model with a onetoone field that is the primary key is + not treated like a derived model + """ + parent = ParentModel(name1='parent name') + associate = AssociatedModel(name='hello', ref=parent) + serializer = AssociatedModelSerializer(associate) + self.assertEqual(set(serializer.data.keys()), + set(['name', 'ref'])) + def test_field_with_dictionary(self): """ Make sure that dictionaries from fields are left intact @@ -250,6 +282,14 @@ class ValidationTests(TestCase): self.assertEqual(serializer.is_valid(), False) self.assertEqual(serializer.errors, {'email': ['This field is required.']}) + def test_multitable_inherited_model(self): + data = { + 'name1': 'parent name', + 'name2': 'child name', + } + serializer = DerivedModelSerializer(data=data) + self.assertEqual(serializer.is_valid(), True) + def test_missing_bool_with_default(self): """Make sure that a boolean value with a 'False' value is not mistaken for not having a default.""" -- cgit v1.2.3 From 354fbc64ba5046ce49d58c8243a4f81caddf3823 Mon Sep 17 00:00:00 2001 From: Mjumbe Wawatu Ukweli Date: Mon, 11 Mar 2013 17:28:44 -0400 Subject: Group the model-inheritance-related tests together --- rest_framework/tests/models.py | 14 ------ rest_framework/tests/multitable_inheritance.py | 16 ++++++ rest_framework/tests/serializer.py | 70 ++++++++++++++------------ 3 files changed, 55 insertions(+), 45 deletions(-) create mode 100644 rest_framework/tests/multitable_inheritance.py (limited to 'rest_framework/tests') diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index fcfe5a0c..f2117538 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -166,17 +166,3 @@ class NullableOneToOneSource(RESTFrameworkModel): name = models.CharField(max_length=100) target = models.OneToOneField(OneToOneTarget, null=True, blank=True, related_name='nullable_source') - - -# Inherited -class ParentModel(RESTFrameworkModel): - name1 = models.CharField(max_length=100) - - -class ChildModel(ParentModel): - name2 = models.CharField(max_length=100) - - -class AssociatedModel(RESTFrameworkModel): - ref = models.OneToOneField(ParentModel, primary_key=True) - name = models.CharField(max_length=100) diff --git a/rest_framework/tests/multitable_inheritance.py b/rest_framework/tests/multitable_inheritance.py new file mode 100644 index 00000000..1cca7823 --- /dev/null +++ b/rest_framework/tests/multitable_inheritance.py @@ -0,0 +1,16 @@ +from __future__ import unicode_literals +from django.db import models +from rest_framework.tests.models import RESTFrameworkModel + + +class ParentModel(RESTFrameworkModel): + name1 = models.CharField(max_length=100) + + +class ChildModel(ParentModel): + name2 = models.CharField(max_length=100) + + +class AssociatedModel(RESTFrameworkModel): + ref = models.OneToOneField(ParentModel, primary_key=True) + name = models.CharField(max_length=100) diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 93909b65..c4a8a899 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -4,8 +4,9 @@ from django.test import TestCase from rest_framework import serializers from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel, BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel, - ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, ParentModel, ChildModel, - AssociatedModel) + ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo) +from rest_framework.tests.multitable_inheritance import (ParentModel, + ChildModel, AssociatedModel) import datetime import pickle @@ -181,27 +182,6 @@ class BasicTests(TestCase): self.assertEqual(set(serializer.data.keys()), set(['name', 'age', 'info'])) - def test_multitable_inherited_model_fields_as_expected(self): - """ - Assert that the parent pointer field is not included in the fields - serialized fields - """ - child = ChildModel(name1='parent name', name2='child name') - serializer = DerivedModelSerializer(child) - self.assertEqual(set(serializer.data.keys()), - set(['name1', 'name2', 'id'])) - - def test_onetoone_primary_key_model_fields_as_expected(self): - """ - Assert that a model with a onetoone field that is the primary key is - not treated like a derived model - """ - parent = ParentModel(name1='parent name') - associate = AssociatedModel(name='hello', ref=parent) - serializer = AssociatedModelSerializer(associate) - self.assertEqual(set(serializer.data.keys()), - set(['name', 'ref'])) - def test_field_with_dictionary(self): """ Make sure that dictionaries from fields are left intact @@ -282,14 +262,6 @@ class ValidationTests(TestCase): self.assertEqual(serializer.is_valid(), False) self.assertEqual(serializer.errors, {'email': ['This field is required.']}) - def test_multitable_inherited_model(self): - data = { - 'name1': 'parent name', - 'name2': 'child name', - } - serializer = DerivedModelSerializer(data=data) - self.assertEqual(serializer.is_valid(), True) - def test_missing_bool_with_default(self): """Make sure that a boolean value with a 'False' value is not mistaken for not having a default.""" @@ -1150,3 +1122,39 @@ class DeserializeListTestCase(TestCase): self.assertFalse(serializer.is_valid()) expected = [{}, {'email': ['This field is required.']}, {}] self.assertEqual(serializer.errors, expected) + + +class IneritedModelSerializationTests(TestCase): + + def test_multitable_inherited_model_fields_as_expected(self): + """ + Assert that the parent pointer field is not included in the fields + serialized fields + """ + child = ChildModel(name1='parent name', name2='child name') + serializer = DerivedModelSerializer(child) + self.assertEqual(set(serializer.data.keys()), + set(['name1', 'name2', 'id'])) + + def test_onetoone_primary_key_model_fields_as_expected(self): + """ + Assert that a model with a onetoone field that is the primary key is + not treated like a derived model + """ + parent = ParentModel(name1='parent name') + associate = AssociatedModel(name='hello', ref=parent) + serializer = AssociatedModelSerializer(associate) + self.assertEqual(set(serializer.data.keys()), + set(['name', 'ref'])) + + def test_data_is_valid_without_parent_ptr(self): + """ + Assert that the pointer to the parent table is not a required field + for input data + """ + data = { + 'name1': 'parent name', + 'name2': 'child name', + } + serializer = DerivedModelSerializer(data=data) + self.assertEqual(serializer.is_valid(), True) -- cgit v1.2.3 From bd3fe75e1a41e45b0c9ff1e39707ee059ad0e06a Mon Sep 17 00:00:00 2001 From: Mjumbe Wawatu Ukweli Date: Mon, 11 Mar 2013 17:32:32 -0400 Subject: Further group model inheritance tests --- rest_framework/tests/multitable_inheritance.py | 51 ++++++++++++++++++++++++++ rest_framework/tests/serializer.py | 48 ------------------------ 2 files changed, 51 insertions(+), 48 deletions(-) (limited to 'rest_framework/tests') diff --git a/rest_framework/tests/multitable_inheritance.py b/rest_framework/tests/multitable_inheritance.py index 1cca7823..00c15327 100644 --- a/rest_framework/tests/multitable_inheritance.py +++ b/rest_framework/tests/multitable_inheritance.py @@ -1,8 +1,11 @@ from __future__ import unicode_literals from django.db import models +from django.test import TestCase +from rest_framework import serializers from rest_framework.tests.models import RESTFrameworkModel +# Models class ParentModel(RESTFrameworkModel): name1 = models.CharField(max_length=100) @@ -14,3 +17,51 @@ class ChildModel(ParentModel): class AssociatedModel(RESTFrameworkModel): ref = models.OneToOneField(ParentModel, primary_key=True) name = models.CharField(max_length=100) + + +# Serializers +class DerivedModelSerializer(serializers.ModelSerializer): + class Meta: + model = ChildModel + + +class AssociatedModelSerializer(serializers.ModelSerializer): + class Meta: + model = AssociatedModel + + +# Tests +class IneritedModelSerializationTests(TestCase): + + def test_multitable_inherited_model_fields_as_expected(self): + """ + Assert that the parent pointer field is not included in the fields + serialized fields + """ + child = ChildModel(name1='parent name', name2='child name') + serializer = DerivedModelSerializer(child) + self.assertEqual(set(serializer.data.keys()), + set(['name1', 'name2', 'id'])) + + def test_onetoone_primary_key_model_fields_as_expected(self): + """ + Assert that a model with a onetoone field that is the primary key is + not treated like a derived model + """ + parent = ParentModel(name1='parent name') + associate = AssociatedModel(name='hello', ref=parent) + serializer = AssociatedModelSerializer(associate) + self.assertEqual(set(serializer.data.keys()), + set(['name', 'ref'])) + + def test_data_is_valid_without_parent_ptr(self): + """ + Assert that the pointer to the parent table is not a required field + for input data + """ + data = { + 'name1': 'parent name', + 'name2': 'child name', + } + serializer = DerivedModelSerializer(data=data) + self.assertEqual(serializer.is_valid(), True) diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index c4a8a899..beb372c2 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -5,8 +5,6 @@ from rest_framework import serializers from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel, BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel, ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo) -from rest_framework.tests.multitable_inheritance import (ParentModel, - ChildModel, AssociatedModel) import datetime import pickle @@ -98,16 +96,6 @@ class BrokenModelSerializer(serializers.ModelSerializer): fields = ['some_field'] -class DerivedModelSerializer(serializers.ModelSerializer): - class Meta: - model = ChildModel - - -class AssociatedModelSerializer(serializers.ModelSerializer): - class Meta: - model = AssociatedModel - - class BasicTests(TestCase): def setUp(self): self.comment = Comment( @@ -1122,39 +1110,3 @@ class DeserializeListTestCase(TestCase): self.assertFalse(serializer.is_valid()) expected = [{}, {'email': ['This field is required.']}, {}] self.assertEqual(serializer.errors, expected) - - -class IneritedModelSerializationTests(TestCase): - - def test_multitable_inherited_model_fields_as_expected(self): - """ - Assert that the parent pointer field is not included in the fields - serialized fields - """ - child = ChildModel(name1='parent name', name2='child name') - serializer = DerivedModelSerializer(child) - self.assertEqual(set(serializer.data.keys()), - set(['name1', 'name2', 'id'])) - - def test_onetoone_primary_key_model_fields_as_expected(self): - """ - Assert that a model with a onetoone field that is the primary key is - not treated like a derived model - """ - parent = ParentModel(name1='parent name') - associate = AssociatedModel(name='hello', ref=parent) - serializer = AssociatedModelSerializer(associate) - self.assertEqual(set(serializer.data.keys()), - set(['name', 'ref'])) - - def test_data_is_valid_without_parent_ptr(self): - """ - Assert that the pointer to the parent table is not a required field - for input data - """ - data = { - 'name1': 'parent name', - 'name2': 'child name', - } - serializer = DerivedModelSerializer(data=data) - self.assertEqual(serializer.is_valid(), True) -- cgit v1.2.3 From 2e481f3318609fff5b884a09cbc9d2c5782deae4 Mon Sep 17 00:00:00 2001 From: Dave Kuhn Date: Tue, 12 Mar 2013 12:00:41 +1100 Subject: Added test for X-HTTP-Method-Override header --- rest_framework/tests/request.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'rest_framework/tests') diff --git a/rest_framework/tests/request.py b/rest_framework/tests/request.py index 4892f7a6..97e5af20 100644 --- a/rest_framework/tests/request.py +++ b/rest_framework/tests/request.py @@ -58,6 +58,14 @@ class TestMethodOverloading(TestCase): request = Request(factory.post('/', {api_settings.FORM_METHOD_OVERRIDE: 'DELETE'})) self.assertEqual(request.method, 'DELETE') + def test_x_http_method_override_header(self): + """ + POST requests can also be overloaded to another method by setting + the X-HTTP-Method-Override header. + """ + request = Request(factory.post('/', {'foo': 'bar'}, HTTP_X_HTTP_METHOD_OVERRIDE='DELETE')) + self.assertEqual(request.method, 'DELETE') + class TestContentParsing(TestCase): def test_standard_behaviour_determines_no_content_GET(self): -- cgit v1.2.3 From a798a5350a6aa3100695d41d4d37ec7e2e073bdd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 13 Mar 2013 11:42:12 +0000 Subject: Fix duplicated database queries for paginated lists. Closes #713. --- rest_framework/tests/pagination.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'rest_framework/tests') diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index 472ffcdd..3c76ca7d 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -73,7 +73,9 @@ class IntegrationTestPagination(TestCase): GET requests to paginated ListCreateAPIView should return paginated results. """ request = factory.get('/') - response = self.view(request).render() + # Note: Database queries are a `SELECT COUNT`, and `SELECT ` + with self.assertNumQueries(2): + response = self.view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 26) self.assertEqual(response.data['results'], self.data[:10]) @@ -81,7 +83,8 @@ class IntegrationTestPagination(TestCase): self.assertEqual(response.data['previous'], None) request = factory.get(response.data['next']) - response = self.view(request).render() + with self.assertNumQueries(2): + response = self.view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 26) self.assertEqual(response.data['results'], self.data[10:20]) @@ -89,7 +92,8 @@ class IntegrationTestPagination(TestCase): self.assertNotEqual(response.data['previous'], None) request = factory.get(response.data['next']) - response = self.view(request).render() + with self.assertNumQueries(2): + response = self.view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 26) self.assertEqual(response.data['results'], self.data[20:]) -- cgit v1.2.3 From 73ab7dc3f18c43d7bfb0c6f7581784d398cb36cf Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 13 Mar 2013 12:45:54 +0000 Subject: Use django-filter 0.6a1 and add database query count tests for paginated, filtered lists. --- rest_framework/tests/pagination.py | 100 +++++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 20 deletions(-) (limited to 'rest_framework/tests') diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index 3c76ca7d..1a2d68a6 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import datetime from decimal import Decimal +import django from django.core.paginator import Paginator from django.test import TestCase from django.test.client import RequestFactory @@ -20,21 +21,6 @@ class RootView(generics.ListCreateAPIView): paginate_by = 10 -if django_filters: - class DecimalFilter(django_filters.FilterSet): - decimal = django_filters.NumberFilter(lookup_type='lt') - - class Meta: - model = FilterableItem - fields = ['text', 'decimal', 'date'] - - class FilterFieldsRootView(generics.ListCreateAPIView): - model = FilterableItem - paginate_by = 10 - filter_class = DecimalFilter - filter_backend = filters.DjangoFilterBackend - - class DefaultPageSizeKwargView(generics.ListAPIView): """ View for testing default paginate_by_param usage @@ -119,17 +105,44 @@ class IntegrationTestPaginationAndFiltering(TestCase): {'id': obj.id, 'text': obj.text, 'decimal': obj.decimal, 'date': obj.date.isoformat()} for obj in self.objects.all() ] - self.view = FilterFieldsRootView.as_view() @unittest.skipUnless(django_filters, 'django-filters not installed') - def test_get_paginated_filtered_root_view(self): + def test_get_django_filter_paginated_filtered_root_view(self): """ GET requests to paginated filtered ListCreateAPIView should return paginated results. The next and previous links should preserve the filtered parameters. """ + class DecimalFilter(django_filters.FilterSet): + decimal = django_filters.NumberFilter(lookup_type='lt') + + class Meta: + model = FilterableItem + fields = ['text', 'decimal', 'date'] + + class FilterFieldsRootView(generics.ListCreateAPIView): + model = FilterableItem + paginate_by = 10 + filter_class = DecimalFilter + filter_backend = filters.DjangoFilterBackend + + view = FilterFieldsRootView.as_view() + + EXPECTED_NUM_QUERIES = 2 + if django.VERSION < (1, 4): + # On Django 1.3 we need to use django-filter 0.5.4 + # + # The filter objects there don't expose a `.count()` method, + # which means we only make a single query *but* it's a single + # query across *all* of the queryset, instead of a COUNT and then + # a SELECT with a LIMIT. + # + # Although this is fewer queries, it's actually a regression. + EXPECTED_NUM_QUERIES = 1 + request = factory.get('/?decimal=15.20') - response = self.view(request).render() + with self.assertNumQueries(EXPECTED_NUM_QUERIES): + response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 15) self.assertEqual(response.data['results'], self.data[:10]) @@ -137,7 +150,8 @@ class IntegrationTestPaginationAndFiltering(TestCase): self.assertEqual(response.data['previous'], None) request = factory.get(response.data['next']) - response = self.view(request).render() + with self.assertNumQueries(EXPECTED_NUM_QUERIES): + response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 15) self.assertEqual(response.data['results'], self.data[10:15]) @@ -145,7 +159,53 @@ class IntegrationTestPaginationAndFiltering(TestCase): self.assertNotEqual(response.data['previous'], None) request = factory.get(response.data['previous']) - response = self.view(request).render() + with self.assertNumQueries(EXPECTED_NUM_QUERIES): + response = view(request).render() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 15) + self.assertEqual(response.data['results'], self.data[:10]) + self.assertNotEqual(response.data['next'], None) + self.assertEqual(response.data['previous'], None) + + def test_get_basic_paginated_filtered_root_view(self): + """ + Same as `test_get_django_filter_paginated_filtered_root_view`, + except using a custom filter backend instead of the django-filter + backend, + """ + + class DecimalFilterBackend(filters.BaseFilterBackend): + def filter_queryset(self, request, queryset, view): + return queryset.filter(decimal__lt=Decimal(request.GET['decimal'])) + + class BasicFilterFieldsRootView(generics.ListCreateAPIView): + model = FilterableItem + paginate_by = 10 + filter_backend = DecimalFilterBackend + + view = BasicFilterFieldsRootView.as_view() + + request = factory.get('/?decimal=15.20') + with self.assertNumQueries(2): + response = view(request).render() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 15) + self.assertEqual(response.data['results'], self.data[:10]) + self.assertNotEqual(response.data['next'], None) + self.assertEqual(response.data['previous'], None) + + request = factory.get(response.data['next']) + with self.assertNumQueries(2): + response = view(request).render() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 15) + self.assertEqual(response.data['results'], self.data[10:15]) + self.assertEqual(response.data['next'], None) + self.assertNotEqual(response.data['previous'], None) + + request = factory.get(response.data['previous']) + with self.assertNumQueries(2): + response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 15) self.assertEqual(response.data['results'], self.data[:10]) -- cgit v1.2.3 From acc8c1faa4f85dda00723d755e56bb3c980dbc75 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 13 Mar 2013 20:40:39 +0000 Subject: force_insert, force_update arguments. Closes #484. Confirmed by `assertNumQueries(…)` in tests. --- rest_framework/tests/generics.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'rest_framework/tests') diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py index 1837898b..f564890c 100644 --- a/rest_framework/tests/generics.py +++ b/rest_framework/tests/generics.py @@ -184,7 +184,7 @@ class TestInstanceView(TestCase): content = {'text': 'foobar'} request = factory.put('/1', json.dumps(content), content_type='application/json') - with self.assertNumQueries(3): + with self.assertNumQueries(2): response = self.view(request, pk='1').render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, {'id': 1, 'text': 'foobar'}) @@ -199,7 +199,7 @@ class TestInstanceView(TestCase): request = factory.patch('/1', json.dumps(content), content_type='application/json') - with self.assertNumQueries(3): + with self.assertNumQueries(2): response = self.view(request, pk=1).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, {'id': 1, 'text': 'foobar'}) @@ -248,7 +248,7 @@ class TestInstanceView(TestCase): content = {'id': 999, 'text': 'foobar'} request = factory.put('/1', json.dumps(content), content_type='application/json') - with self.assertNumQueries(3): + with self.assertNumQueries(2): response = self.view(request, pk=1).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, {'id': 1, 'text': 'foobar'}) @@ -264,7 +264,7 @@ class TestInstanceView(TestCase): content = {'text': 'foobar'} request = factory.put('/1', json.dumps(content), content_type='application/json') - with self.assertNumQueries(4): + with self.assertNumQueries(3): response = self.view(request, pk=1).render() self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data, {'id': 1, 'text': 'foobar'}) @@ -280,7 +280,7 @@ class TestInstanceView(TestCase): # pk fields can not be created on demand, only the database can set the pk for a new object request = factory.put('/5', json.dumps(content), content_type='application/json') - with self.assertNumQueries(4): + with self.assertNumQueries(3): response = self.view(request, pk=5).render() self.assertEqual(response.status_code, status.HTTP_201_CREATED) new_obj = self.objects.get(pk=5) -- cgit v1.2.3