Skip to content

Commit 99537be

Browse files
committed
Merge pull request #88 from django-json-api/feature/test-all-versions
Automatically test combinations of Python 2.7+, Django 1.6+, and DRF 3.1+
2 parents b51d361 + 9ba86d0 commit 99537be

25 files changed

+3574
-179
lines changed

.gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,7 @@ pip-log.txt
2626
pip-delete-this-directory.txt
2727

2828
# Pycharm project files
29-
.idea/
29+
.idea/
30+
31+
# Tox
32+
.tox/

.travis.yml

+25-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,27 @@
11
language: python
22
sudo: false
3-
python:
4-
- "2.7"
5-
- "3.3"
6-
- "3.4"
7-
install:
8-
- pip install -e .
9-
script: python runtests.py
3+
install: pip install tox
4+
script: tox
5+
env:
6+
- TOXENV=py27-django16-drf31
7+
- TOXENV=py27-django16-drf32
8+
- TOXENV=py32-django16-drf31
9+
- TOXENV=py32-django16-drf32
10+
- TOXENV=py33-django16-drf31
11+
- TOXENV=py33-django16-drf32
12+
- TOXENV=py27-django17-drf31
13+
- TOXENV=py27-django17-drf32
14+
- TOXENV=py32-django17-drf31
15+
- TOXENV=py32-django17-drf32
16+
- TOXENV=py33-django17-drf31
17+
- TOXENV=py33-django17-drf32
18+
- TOXENV=py34-django17-drf31
19+
- TOXENV=py34-django17-drf32
20+
- TOXENV=py27-django18-drf31
21+
- TOXENV=py27-django18-drf32
22+
- TOXENV=py32-django18-drf31
23+
- TOXENV=py32-django18-drf32
24+
- TOXENV=py33-django18-drf31
25+
- TOXENV=py33-django18-drf32
26+
- TOXENV=py34-django18-drf31
27+
- TOXENV=py34-django18-drf32

docs/getting-started.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ like the following:
5353

5454
1. Python >= 2.7
5555
2. Django
56-
3. Django REST Framework >= 2.4
56+
3. Django REST Framework >= 3.1
5757

5858
## Installation
5959

example/api/resources/identity.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from django.contrib.auth import models as auth_models
2+
from django.utils import encoding
3+
24
from rest_framework import viewsets, generics, renderers, parsers, serializers
35
from rest_framework.decorators import list_route, detail_route
46
from rest_framework.response import Response
@@ -31,8 +33,8 @@ def posts(self, request):
3133
posts = [{'id': 1, 'title': 'Test Blog Post'}]
3234

3335
data = {
34-
u'identities': IdentitySerializer(identities, many=True).data,
35-
u'posts': PostSerializer(posts, many=True).data,
36+
encoding.force_text('identities'): IdentitySerializer(identities, many=True).data,
37+
encoding.force_text('posts'): PostSerializer(posts, many=True).data,
3638
}
3739
return Response(utils.format_keys(data, format_type='camelize'))
3840

example/api/urls.py

-18
Original file line numberDiff line numberDiff line change
@@ -1,18 +0,0 @@
1-
"""
2-
Example app URLs
3-
"""
4-
from django.conf.urls import patterns, include, url
5-
from rest_framework import routers
6-
from .resources.identity import Identity, GenericIdentity
7-
8-
router = routers.DefaultRouter(trailing_slash=False)
9-
10-
router.register(r'identities', Identity)
11-
12-
urlpatterns = router.urls
13-
14-
urlpatterns += patterns('',
15-
url(r'identities/default/(?P<pk>\d+)',
16-
GenericIdentity.as_view(), name='user-default'),
17-
)
18-

example/factories/__init__.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# -*- encoding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
import factory
5+
6+
from example.models import Blog
7+
8+
9+
class BlogFactory(factory.django.DjangoModelFactory):
10+
class Meta:
11+
model = Blog
12+
13+
name = "Blog 1"

example/models.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# -*- encoding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from django.db import models
5+
from django.utils.encoding import python_2_unicode_compatible
6+
7+
8+
class BaseModel(models.Model):
9+
"""
10+
I hear RoR has this by default, who doesn't need these two fields!
11+
"""
12+
created_at = models.DateTimeField(auto_now_add=True)
13+
modified_at = models.DateTimeField(auto_now=True)
14+
15+
class Meta:
16+
abstract = True
17+
18+
19+
@python_2_unicode_compatible
20+
class Blog(BaseModel):
21+
name = models.CharField(max_length=100)
22+
tagline = models.TextField()
23+
24+
def __str__(self):
25+
return self.name
26+
27+
28+
@python_2_unicode_compatible
29+
class Author(BaseModel):
30+
name = models.CharField(max_length=50)
31+
email = models.EmailField()
32+
33+
def __str__(self):
34+
return self.name
35+
36+
37+
@python_2_unicode_compatible
38+
class Entry(BaseModel):
39+
blog = models.ForeignKey(Blog)
40+
headline = models.CharField(max_length=255)
41+
body_text = models.TextField()
42+
pub_date = models.DateField()
43+
mod_date = models.DateField()
44+
authors = models.ManyToManyField(Author)
45+
n_comments = models.IntegerField()
46+
n_pingbacks = models.IntegerField()
47+
rating = models.IntegerField()
48+
49+
def __str__(self):
50+
return self.headline

example/serializers.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from rest_framework import serializers
2+
3+
4+
class BlogSerializer(serializers.Serializer):
5+
6+
class Meta:
7+
fields = ('name', )

example/settings.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
'django.contrib.auth',
2323
'django.contrib.admin',
2424
'rest_framework',
25-
'example.api',
25+
'example',
2626
]
2727

28-
ROOT_URLCONF = 'example.api.urls'
28+
ROOT_URLCONF = 'example.urls'
2929

3030
SECRET_KEY = 'abc123'
3131

example/tests/conftest.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from pytest_factoryboy import register
2+
3+
from example.factories import BlogFactory
4+
5+
register(BlogFactory)

example/tests/test_factories.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import pytest
2+
3+
from example.models import Blog
4+
from example.factories import BlogFactory
5+
6+
pytestmark = pytest.mark.django_db
7+
8+
9+
def test_factory_instance(blog_factory):
10+
11+
assert blog_factory == BlogFactory
12+
13+
14+
def test_model_instance(blog):
15+
16+
assert isinstance(blog, Blog)
17+
18+
19+
def test_blog_name(blog):
20+
assert blog.name == 'Blog 1'
21+
22+
23+
def test_multiple_blog(blog_factory):
24+
another_blog = blog_factory(name='Cool Blog')
25+
new_blog = blog_factory(name='Awesome Blog')
26+
27+
assert another_blog.name == 'Cool Blog'
28+
assert new_blog.name == 'Awesome Blog'

example/tests/test_format_keys.py

+32-18
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import json
2-
3-
from example.tests import TestBase
4-
51
from django.contrib.auth import get_user_model
62
from django.core.urlresolvers import reverse
73
from django.conf import settings
4+
from django.utils import encoding
5+
6+
from example.tests import TestBase
7+
from example.tests.utils import dump_json, redump_json
88

99

1010
class FormatKeysSetTests(TestBase):
@@ -18,7 +18,8 @@ def setUp(self):
1818
self.detail_url = reverse('user-detail', kwargs={'pk': self.miles.pk})
1919

2020
# Set the format keys settings.
21-
setattr(settings, 'JSON_API_FORMAT_KEYS', 'camelization')
21+
setattr(settings, 'JSON_API_FORMAT_KEYS', 'camelize')
22+
# CAMELIZE capitalize the type, needs to be checked
2223

2324
def tearDown(self):
2425
# Remove the format keys settings.
@@ -34,20 +35,33 @@ def test_camelization(self):
3435

3536
user = get_user_model().objects.all()[0]
3637
expected = {
37-
u'data': {
38-
u'type': u'users',
39-
u'id': user.pk,
40-
u'attributes': {
41-
u'firstName': user.first_name,
42-
u'lastName': user.last_name,
43-
u'email': user.email
44-
},
38+
'data': [
39+
{
40+
'type': 'Users',
41+
'id': encoding.force_text(user.pk),
42+
'attributes': {
43+
'firstName': user.first_name,
44+
'lastName': user.last_name,
45+
'email': user.email
46+
},
47+
}
48+
],
49+
'links': {
50+
'first': 'http://testserver/identities?page=1',
51+
'last': 'http://testserver/identities?page=2',
52+
'next': 'http://testserver/identities?page=2',
53+
'prev': None
54+
},
55+
'meta': {
56+
'pagination': {
57+
'page': 1,
58+
'pages': 2,
59+
'count': 2
60+
}
4561
}
4662
}
4763

48-
json_content = json.loads(response.content.decode('utf8'))
49-
links = json_content.get('links')
64+
content_dump = redump_json(response.content)
65+
expected_dump = dump_json(expected)
5066

51-
self.assertEquals(expected.get('users'), json_content.get('users'))
52-
self.assertEqual(u'http://testserver/identities?page=2',
53-
links.get('next'))
67+
assert expected_dump == content_dump

example/tests/test_generic_validation.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import json
2-
from example.tests import TestBase
2+
33
from django.core.urlresolvers import reverse
44
from django.conf import settings
5+
56
from rest_framework.serializers import ValidationError
67

8+
from example.tests import TestBase
9+
from example.tests.utils import dump_json, redump_json
10+
711

812
class GenericValidationTest(TestBase):
913
"""
@@ -20,7 +24,6 @@ def test_generic_validation_error(self):
2024
response = self.client.get(self.url)
2125
self.assertEqual(response.status_code, 400)
2226

23-
result = json.loads(response.content.decode('utf8'))
2427
expected = {
2528
'errors': [{
2629
'status': '400',
@@ -30,4 +33,8 @@ def test_generic_validation_error(self):
3033
'detail': 'Oh nohs!'
3134
}]
3235
}
33-
self.assertEqual(result, expected)
36+
37+
content_dump = redump_json(response.content)
38+
expected_dump = dump_json(expected)
39+
40+
assert expected_dump == content_dump

0 commit comments

Comments
 (0)