Skip to content

Commit 9d65b13

Browse files
authored
Merge pull request #35 from SweetProcess/tests-for-caching-property
Adding cached_property to the fields method
2 parents bda3ae9 + 325a45c commit 9d65b13

File tree

7 files changed

+171
-80
lines changed

7 files changed

+171
-80
lines changed

drf_dynamic_fields/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import warnings
55

66
from django.conf import settings
7+
from django.utils.functional import cached_property
78

89

910
class DynamicFieldsMixin(object):
@@ -12,7 +13,7 @@ class DynamicFieldsMixin(object):
1213
which fields should be displayed.
1314
"""
1415

15-
@property
16+
@cached_property
1617
def fields(self):
1718
"""
1819
Filters the fields according to the `fields` query parameter.

tests/models.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55

66

77
class Teacher(models.Model):
8-
"""No fields, no fun."""
8+
name = models.CharField(max_length=30)
9+
age = models.IntegerField()
910

1011

1112
class School(models.Model):
1213
"""Schools just have teachers, no students."""
14+
15+
name = models.CharField(max_length=30)
1316
teachers = models.ManyToManyField(Teacher)

tests/serializers.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,15 @@ class TeacherSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
1818

1919
class Meta:
2020
model = Teacher
21-
fields = ('id', 'request_info')
21+
fields = ("id", "request_info", "age", "name")
2222

2323
def get_request_info(self, teacher):
2424
"""
2525
a meaningless method that attempts
2626
to access the request object.
2727
"""
28-
request = self.context['request']
29-
return request.build_absolute_uri(
30-
'/api/v1/teacher/{}'.format(teacher.pk)
31-
)
28+
request = self.context["request"]
29+
return request.build_absolute_uri("/api/v1/teacher/{}".format(teacher.pk))
3230

3331

3432
class SchoolSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
@@ -41,4 +39,4 @@ class SchoolSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
4139

4240
class Meta:
4341
model = School
44-
fields = ('id', 'teachers')
42+
fields = ("id", "teachers", "name")

tests/test_mixins.py

Lines changed: 74 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
"""
55
test_drf-dynamic-fields
6-
------------
6+
-----------
77
88
Tests for `drf-dynamic-fields` mixins
99
"""
@@ -25,147 +25,153 @@ def test_removes_fields(self):
2525
Does it actually remove fields?
2626
"""
2727
rf = RequestFactory()
28-
request = rf.get('/api/v1/schools/1/?fields=id')
29-
serializer = TeacherSerializer(context={'request': request})
28+
request = rf.get("/api/v1/schools/1/?fields=id")
29+
serializer = TeacherSerializer(context={"request": request})
3030

31-
self.assertEqual(
32-
set(serializer.fields.keys()),
33-
set(('id',))
34-
)
31+
self.assertEqual(set(serializer.fields.keys()), set(("id",)))
3532

3633
def test_fields_left_alone(self):
3734
"""
3835
What if no fields param is passed? It should not touch the fields.
3936
"""
4037
rf = RequestFactory()
41-
request = rf.get('/api/v1/schools/1/')
42-
serializer = TeacherSerializer(context={'request': request})
38+
request = rf.get("/api/v1/schools/1/")
39+
serializer = TeacherSerializer(context={"request": request})
4340

4441
self.assertEqual(
45-
set(serializer.fields.keys()),
46-
set(('id', 'request_info'))
42+
set(serializer.fields.keys()), set(("id", "request_info", "age", "name"))
4743
)
4844

4945
def test_fields_all_gone(self):
5046
"""
5147
If we pass a blank fields list, then no fields should return.
5248
"""
5349
rf = RequestFactory()
54-
request = rf.get('/api/v1/schools/1/?fields')
55-
serializer = TeacherSerializer(context={'request': request})
50+
request = rf.get("/api/v1/schools/1/?fields")
51+
serializer = TeacherSerializer(context={"request": request})
5652

57-
self.assertEqual(
58-
set(serializer.fields.keys()),
59-
set()
60-
)
53+
self.assertEqual(set(serializer.fields.keys()), set())
6154

6255
def test_ordinary_serializer(self):
6356
"""
6457
Check the full JSON output of the serializer.
6558
"""
6659
rf = RequestFactory()
67-
request = rf.get('/api/v1/schools/1/?fields=id')
68-
teacher = Teacher.objects.create()
60+
request = rf.get("/api/v1/schools/1/?fields=id,age")
61+
teacher = Teacher.objects.create(name="Susan", age=34)
6962

70-
serializer = TeacherSerializer(teacher, context={'request': request})
63+
serializer = TeacherSerializer(teacher, context={"request": request})
7164

72-
self.assertEqual(
73-
serializer.data, {
74-
'id': teacher.id
75-
}
76-
)
65+
self.assertEqual(serializer.data, {"id": teacher.id, "age": teacher.age})
7766

7867
def test_omit(self):
7968
"""
8069
Check a basic usage of omit.
8170
"""
8271
rf = RequestFactory()
83-
request = rf.get('/api/v1/schools/1/?omit=request_info')
84-
serializer = TeacherSerializer(context={'request': request})
72+
request = rf.get("/api/v1/schools/1/?omit=request_info")
73+
serializer = TeacherSerializer(context={"request": request})
8574

86-
self.assertEqual(
87-
set(serializer.fields.keys()),
88-
set(('id',))
89-
)
75+
self.assertEqual(set(serializer.fields.keys()), set(("id", "name", "age")))
9076

9177
def test_omit_and_fields_used(self):
9278
"""
9379
Can they be used together.
9480
"""
9581
rf = RequestFactory()
96-
request = rf.get('/api/v1/schools/1/?fields=id,request_info&omit=request_info')
97-
serializer = TeacherSerializer(context={'request': request})
82+
request = rf.get("/api/v1/schools/1/?fields=id,request_info&omit=request_info")
83+
serializer = TeacherSerializer(context={"request": request})
9884

99-
self.assertEqual(
100-
set(serializer.fields.keys()),
101-
set(('id',))
102-
)
85+
self.assertEqual(set(serializer.fields.keys()), set(("id",)))
10386

10487
def test_omit_everything(self):
10588
"""
10689
Can remove it all tediously.
10790
"""
10891
rf = RequestFactory()
109-
request = rf.get('/api/v1/schools/1/?omit=id,request_info')
110-
serializer = TeacherSerializer(context={'request': request})
92+
request = rf.get("/api/v1/schools/1/?omit=id,request_info,age,name")
93+
serializer = TeacherSerializer(context={"request": request})
11194

112-
self.assertEqual(
113-
set(serializer.fields.keys()),
114-
set()
115-
)
95+
self.assertEqual(set(serializer.fields.keys()), set())
11696

11797
def test_omit_nothing(self):
11898
"""
11999
Blank omit doesn't affect anything.
120100
"""
121101
rf = RequestFactory()
122-
request = rf.get('/api/v1/schools/1/?omit')
123-
serializer = TeacherSerializer(context={'request': request})
102+
request = rf.get("/api/v1/schools/1/?omit")
103+
serializer = TeacherSerializer(context={"request": request})
124104

125105
self.assertEqual(
126-
set(serializer.fields.keys()),
127-
set(('id', 'request_info'))
106+
set(serializer.fields.keys()), set(("id", "request_info", "name", "age"))
128107
)
129108

130109
def test_omit_non_existant_field(self):
131110
rf = RequestFactory()
132-
request = rf.get('/api/v1/schools/1/?omit=pretend')
133-
serializer = TeacherSerializer(context={'request': request})
111+
request = rf.get("/api/v1/schools/1/?omit=pretend")
112+
serializer = TeacherSerializer(context={"request": request})
134113

135114
self.assertEqual(
136-
set(serializer.fields.keys()),
137-
set(('id', 'request_info'))
115+
set(serializer.fields.keys()), set(("id", "request_info", "name", "age"))
138116
)
139117

140118
def test_as_nested_serializer(self):
141119
"""
142120
Nested serializers are not filtered.
143121
"""
144122
rf = RequestFactory()
145-
request = rf.get('/api/v1/schools/1/?fields=teachers')
123+
request = rf.get("/api/v1/schools/1/?fields=teachers")
146124

147-
school = School.objects.create()
125+
school = School.objects.create(name="Python Heights High")
148126
teachers = [
149-
Teacher.objects.create(),
150-
Teacher.objects.create()
127+
Teacher.objects.create(name="Shane", age=45),
128+
Teacher.objects.create(name="Kaz", age=29),
151129
]
152130
school.teachers.add(*teachers)
153131

154-
serializer = SchoolSerializer(school, context={'request': request})
132+
serializer = SchoolSerializer(school, context={"request": request})
155133

156-
request_info = 'http://testserver/api/v1/teacher/{}'
134+
request_info = "http://testserver/api/v1/teacher/{}"
157135

158136
self.assertEqual(
159-
serializer.data, {
160-
'teachers': [
161-
OrderedDict([
162-
('id', teachers[0].id),
163-
('request_info', request_info.format(teachers[0].id))
164-
]),
165-
OrderedDict([
166-
('id', teachers[1].id),
167-
('request_info', request_info.format(teachers[1].id))
168-
])
137+
serializer.data,
138+
{
139+
"teachers": [
140+
OrderedDict(
141+
[
142+
("id", teachers[0].id),
143+
("request_info", request_info.format(teachers[0].id)),
144+
("age", teachers[0].age),
145+
("name", teachers[0].name),
146+
]
147+
),
148+
OrderedDict(
149+
[
150+
("id", teachers[1].id),
151+
("request_info", request_info.format(teachers[1].id)),
152+
("age", teachers[1].age),
153+
("name", teachers[1].name),
154+
]
155+
),
169156
],
170-
}
157+
},
171158
)
159+
160+
def test_serializer_reuse_with_changing_request(self):
161+
"""
162+
`fields` is a cached property. Changing the request on an already
163+
instantiated serializer will not result in a changed fields attribute.
164+
165+
This was a deliberate choice we have made in favor of speeding up
166+
access to the slow `fields` attribute.
167+
"""
168+
169+
rf = RequestFactory()
170+
request = rf.get("/api/v1/schools/1/?fields=id")
171+
serializer = TeacherSerializer(context={"request": request})
172+
self.assertEqual(set(serializer.fields.keys()), {"id"})
173+
174+
# now change the request on this instantiated serializer.
175+
request2 = rf.get("/api/v1/schools/1/?fields=id,name")
176+
serializer.context["request"] = request2
177+
self.assertEqual(set(serializer.fields.keys()), {"id"})

tests/test_requests.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
"""
5+
test_drf-dynamic-fields
6+
------------
7+
8+
Test for the full request cycle using dynamic fields mixns
9+
"""
10+
from collections import OrderedDict
11+
12+
from django.test import TestCase, RequestFactory
13+
14+
from rest_framework.reverse import reverse
15+
16+
from .serializers import SchoolSerializer, TeacherSerializer
17+
from .models import Teacher, School
18+
19+
20+
class TestDynamicFieldsViews(TestCase):
21+
"""
22+
Testing using dynamic fields in request framework views.
23+
"""
24+
25+
def setUp(self):
26+
"""
27+
Create some teachers and schools.
28+
"""
29+
teachers = [("Craig", 34), ("Kaz", 29), ("Sun", 62)]
30+
31+
schools = ["Python Heights High", "Ruby Consolidated", "Java Coffee School"]
32+
33+
t = [Teacher.objects.create(name=name, age=age) for name, age in teachers]
34+
35+
for name in schools:
36+
s = School.objects.create(name=name)
37+
s.teachers.add(*t)
38+
39+
def test_teacher_basic(self):
40+
41+
response = self.client.get(reverse("teacher-list"))
42+
for teacher in response.data:
43+
self.assertEqual(teacher.keys(), {"id", "request_info", "age", "name"})
44+
45+
def test_teacher_fields(self):
46+
47+
response = self.client.get(reverse("teacher-list"), {"fields": "id,age"})
48+
for teacher in response.data:
49+
self.assertEqual(teacher.keys(), {"id", "age"})
50+
51+
def test_teacher_omit(self):
52+
53+
response = self.client.get(reverse("teacher-list"), {"omit": "id,age"})
54+
for teacher in response.data:
55+
self.assertEqual(teacher.keys(), {"request_info", "name"})
56+
57+
def test_nested_teacher_fields(self):
58+
59+
response = self.client.get(reverse("school-list"), {"fields": "name,teachers"})
60+
for school in response.data:
61+
self.assertEqual(school.keys(), {"teachers", "name"})
62+
self.assertEqual(
63+
school["teachers"][0].keys(), {"id", "request_info", "age", "name"}
64+
)

tests/urls.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# -*- coding: utf-8
2-
"""
3-
Empty urls for test.
4-
"""
5-
urlpatterns = [] # noqa.
2+
from rest_framework import routers
3+
4+
from .views import SchoolViewSet, TeacherViewSet
5+
6+
router = routers.SimpleRouter()
7+
router.register("teachers", TeacherViewSet)
8+
router.register("schools", SchoolViewSet)
9+
10+
urlpatterns = router.urls

tests/views.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from rest_framework.viewsets import ModelViewSet
2+
3+
from .models import School, Teacher
4+
from .serializers import SchoolSerializer, TeacherSerializer
5+
6+
7+
class TeacherViewSet(ModelViewSet):
8+
queryset = Teacher.objects.all()
9+
serializer_class = TeacherSerializer
10+
11+
12+
class SchoolViewSet(ModelViewSet):
13+
queryset = School.objects.all()
14+
serializer_class = SchoolSerializer

0 commit comments

Comments
 (0)