Skip to content

Commit e5dc7f6

Browse files
committed
Facelift, support for py3 and modern django version, wheel support. (#61)
* Facelift, support for py3 and modern django version, wheel support. * Add support for Python 3 and PyPy * Move to pytest for testing * Add wheel build support * Drops support for Django < 1.6, adds support for Django 1.6, 1.7 1.8 and 1.9 This adds a proper tox config and uses this in travis, moves to pytest instead of nose and cleans up some old stuff.
1 parent 684f1d5 commit e5dc7f6

17 files changed

+506
-332
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
*.egg-info
33
*.db
44
*.sw[po]
5+
.cache
6+
.tox
57
dist

.travis.yml

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
1+
sudo: false
2+
13
language: python
2-
env:
3-
- DJANGO_VERSION=1.4.5
4-
- DJANGO_VERSION=1.5.1
4+
55
python:
6-
- "2.6"
7-
- "2.7"
8-
install: pip install -q Django==${DJANGO_VERSION} --use-mirrors
9-
script: ./run.sh test
6+
- '2.7'
7+
- '3.4'
8+
- '3.5'
9+
- pypy
10+
11+
env:
12+
- DJANGO_VERSION=1.9.x
13+
- DJANGO_VERSION=1.8.x
14+
- DJANGO_VERSION=1.7.x
15+
- DJANGO_VERSION=1.6.x
16+
17+
matrix:
18+
exclude:
19+
- python: '3.5'
20+
env: DJANGO_VERSION=1.7.x
21+
- python: '3.5'
22+
env: DJANGO_VERSION=1.6.x
23+
24+
install:
25+
- pip install tox
26+
27+
script:
28+
- tox -e "$TRAVIS_PYTHON_VERSION-$DJANGO_VERSION"

CHANGES

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
CHANGES
33
=======
44

5+
v3.0
6+
====
7+
8+
Currenty in development.
9+
10+
* Add support for Python 3 and PyPy
11+
* Move to pytest for testing
12+
* Add wheel build support
13+
* Drops support for Django < 1.6, adds support for Django 1.6, 1.7, 1.8 and 1.9
14+
15+
516
v2.0.3
617
======
718

csp/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +0,0 @@
1-
VERSION = (2, 0, 3)
2-
__version__ = '.'.join(map(str, VERSION))

csp/tests/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +0,0 @@
1-
from csp.tests.test_decorators import DecoratorTests # noqa
2-
from csp.tests.test_middleware import MiddlewareTests # noqa
3-
from csp.tests.test_utils import UtilsTests # noqa

csp/tests/settings.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
CSP_REPORT_ONLY = False
2+
3+
DATABASES = {
4+
'default': {
5+
'NAME': 'test.db',
6+
'ENGINE': 'django.db.backends.sqlite3',
7+
}
8+
}
9+
10+
INSTALLED_APPS = (
11+
'django.contrib.admin',
12+
'django.contrib.auth',
13+
'django.contrib.contenttypes',
14+
'django.contrib.sessions',
15+
'django.contrib.sites',
16+
'csp',
17+
)
18+
19+
SECRET_KEY = 'csp-test-key'

csp/tests/test_decorators.py

Lines changed: 56 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,65 @@
11
from django.http import HttpResponse
2-
from django.test import RequestFactory, TestCase
2+
from django.test import RequestFactory
33
from django.test.utils import override_settings
44

5-
from nose.tools import eq_
6-
75
from csp.decorators import csp, csp_replace, csp_update, csp_exempt
86
from csp.middleware import CSPMiddleware
97

8+
109
REQUEST = RequestFactory().get('/')
1110
mw = CSPMiddleware()
1211

13-
class DecoratorTests(TestCase):
14-
def test_csp_exempt(self):
15-
@csp_exempt
16-
def view(request):
17-
return HttpResponse()
18-
response = view(REQUEST)
19-
assert response._csp_exempt
20-
21-
@override_settings(CSP_IMG_SRC=['foo.com'])
22-
def test_csp_update(self):
23-
@csp_update(IMG_SRC='bar.com')
24-
def view(request):
25-
return HttpResponse()
26-
response = view(REQUEST)
27-
eq_(response._csp_update, {'img-src': 'bar.com'})
28-
29-
@override_settings(CSP_IMG_SRC=['foo.com'])
30-
def test_csp_replace(self):
31-
@csp_replace(IMG_SRC='bar.com')
32-
def view(request):
33-
return HttpResponse()
34-
response = view(REQUEST)
35-
eq_(response._csp_replace, {'img-src': 'bar.com'})
36-
37-
def test_csp(self):
38-
@csp(IMG_SRC=['foo.com'], FONT_SRC=['bar.com'])
39-
def view(request):
40-
return HttpResponse()
41-
response = view(REQUEST)
42-
eq_(response._csp_config,
43-
{'img-src': ['foo.com'], 'font-src': ['bar.com']})
44-
mw.process_response(REQUEST, response)
45-
policy_list = sorted(response['Content-Security-Policy'].split("; "))
46-
eq_(policy_list, ["font-src bar.com", "img-src foo.com"])
47-
48-
def test_csp_string_values(self):
49-
# Test backwards compatibility where values were strings
50-
@csp(IMG_SRC='foo.com', FONT_SRC='bar.com')
51-
def view(request):
52-
return HttpResponse()
53-
response = view(REQUEST)
54-
eq_(response._csp_config,
55-
{'img-src': ['foo.com'], 'font-src': ['bar.com']})
56-
mw.process_response(REQUEST, response)
57-
policy_list = sorted(response['Content-Security-Policy'].split("; "))
58-
eq_(policy_list, ["font-src bar.com", "img-src foo.com"])
12+
13+
def test_csp_exempt():
14+
@csp_exempt
15+
def view(request):
16+
return HttpResponse()
17+
response = view(REQUEST)
18+
assert response._csp_exempt
19+
20+
21+
@override_settings(CSP_IMG_SRC=['foo.com'])
22+
def test_csp_update():
23+
@csp_update(IMG_SRC='bar.com')
24+
def view(request):
25+
return HttpResponse()
26+
response = view(REQUEST)
27+
assert response._csp_update == {'img-src': 'bar.com'}
28+
29+
30+
@override_settings(CSP_IMG_SRC=['foo.com'])
31+
def test_csp_replace():
32+
@csp_replace(IMG_SRC='bar.com')
33+
def view(request):
34+
return HttpResponse()
35+
response = view(REQUEST)
36+
assert response._csp_replace == {'img-src': 'bar.com'}
37+
38+
39+
def test_csp():
40+
@csp(IMG_SRC=['foo.com'], FONT_SRC=['bar.com'])
41+
def view(request):
42+
return HttpResponse()
43+
response = view(REQUEST)
44+
assert response._csp_config == {
45+
'img-src': ['foo.com'], 'font-src': ['bar.com']
46+
}
47+
48+
mw.process_response(REQUEST, response)
49+
policy_list = sorted(response['Content-Security-Policy'].split("; "))
50+
assert policy_list == ["font-src bar.com", "img-src foo.com"]
51+
52+
53+
def test_csp_string_values():
54+
# Test backwards compatibility where values were strings
55+
@csp(IMG_SRC='foo.com', FONT_SRC='bar.com')
56+
def view(request):
57+
return HttpResponse()
58+
response = view(REQUEST)
59+
assert response._csp_config == {
60+
'img-src': ['foo.com'], 'font-src': ['bar.com']
61+
}
62+
63+
mw.process_response(REQUEST, response)
64+
policy_list = sorted(response['Content-Security-Policy'].split("; "))
65+
assert policy_list == ["font-src bar.com", "img-src foo.com"]

csp/tests/test_middleware.py

Lines changed: 72 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
from django.http import HttpResponse, HttpResponseServerError
2-
from django.test import RequestFactory, TestCase
2+
from django.test import RequestFactory
33
from django.test.utils import override_settings
44

5-
from nose.tools import eq_
6-
75
from csp.middleware import CSPMiddleware
86

97

@@ -12,67 +10,74 @@
1210
rf = RequestFactory()
1311

1412

15-
class MiddlewareTests(TestCase):
16-
def test_add_header(self):
17-
request = rf.get('/')
18-
response = HttpResponse()
19-
mw.process_response(request, response)
20-
assert HEADER in response
21-
22-
def test_exempt(self):
23-
request = rf.get('/')
24-
response = HttpResponse()
25-
response._csp_exempt = True
26-
mw.process_response(request, response)
27-
assert HEADER not in response
28-
29-
@override_settings(CSP_EXCLUDE_URL_PREFIXES=('/inlines-r-us'))
30-
def text_exclude(self):
31-
request = rf.get('/inlines-r-us/foo')
32-
response = HttpResponse()
33-
mw.process_response(request, response)
34-
assert HEADER not in response
35-
36-
@override_settings(CSP_REPORT_ONLY=True)
37-
def test_report_only(self):
38-
request = rf.get('/')
39-
response = HttpResponse()
40-
mw.process_response(request, response)
41-
assert HEADER not in response
42-
assert HEADER + '-Report-Only' in response
43-
44-
def test_dont_replace(self):
45-
request = rf.get('/')
46-
response = HttpResponse()
47-
response[HEADER] = 'default-src example.com'
48-
mw.process_response(request, response)
49-
eq_(response[HEADER], 'default-src example.com')
50-
51-
def test_use_config(self):
52-
request = rf.get('/')
53-
response = HttpResponse()
54-
response._csp_config = {'default-src': ['example.com']}
55-
mw.process_response(request, response)
56-
eq_(response[HEADER], 'default-src example.com')
57-
58-
def test_use_update(self):
59-
request = rf.get('/')
60-
response = HttpResponse()
61-
response._csp_update = {'default-src': ['example.com']}
62-
mw.process_response(request, response)
63-
eq_(response[HEADER], "default-src 'self' example.com")
64-
65-
@override_settings(CSP_IMG_SRC=['foo.com'])
66-
def test_use_replace(self):
67-
request = rf.get('/')
68-
response = HttpResponse()
69-
response._csp_replace = {'img-src': ['bar.com']}
70-
mw.process_response(request, response)
71-
eq_(response[HEADER], "default-src 'self'; img-src bar.com")
72-
73-
@override_settings(DEBUG=True)
74-
def test_debug_exempt(self):
75-
request = rf.get('/')
76-
response = HttpResponseServerError()
77-
mw.process_response(request, response)
78-
assert HEADER not in response
13+
def test_add_header():
14+
request = rf.get('/')
15+
response = HttpResponse()
16+
mw.process_response(request, response)
17+
assert HEADER in response
18+
19+
20+
def test_exempt():
21+
request = rf.get('/')
22+
response = HttpResponse()
23+
response._csp_exempt = True
24+
mw.process_response(request, response)
25+
assert HEADER not in response
26+
27+
28+
@override_settings(CSP_EXCLUDE_URL_PREFIXES=('/inlines-r-us'))
29+
def text_exclude():
30+
request = rf.get('/inlines-r-us/foo')
31+
response = HttpResponse()
32+
mw.process_response(request, response)
33+
assert HEADER not in response
34+
35+
36+
@override_settings(CSP_REPORT_ONLY=True)
37+
def test_report_only():
38+
request = rf.get('/')
39+
response = HttpResponse()
40+
mw.process_response(request, response)
41+
assert HEADER not in response
42+
assert HEADER + '-Report-Only' in response
43+
44+
45+
def test_dont_replace():
46+
request = rf.get('/')
47+
response = HttpResponse()
48+
response[HEADER] = 'default-src example.com'
49+
mw.process_response(request, response)
50+
assert response[HEADER] == 'default-src example.com'
51+
52+
53+
def test_use_config():
54+
request = rf.get('/')
55+
response = HttpResponse()
56+
response._csp_config = {'default-src': ['example.com']}
57+
mw.process_response(request, response)
58+
assert response[HEADER] == 'default-src example.com'
59+
60+
61+
def test_use_update():
62+
request = rf.get('/')
63+
response = HttpResponse()
64+
response._csp_update = {'default-src': ['example.com']}
65+
mw.process_response(request, response)
66+
assert response[HEADER] == "default-src 'self' example.com"
67+
68+
69+
@override_settings(CSP_IMG_SRC=['foo.com'])
70+
def test_use_replace():
71+
request = rf.get('/')
72+
response = HttpResponse()
73+
response._csp_replace = {'img-src': ['bar.com']}
74+
mw.process_response(request, response)
75+
assert response[HEADER] == "default-src 'self'; img-src bar.com"
76+
77+
78+
@override_settings(DEBUG=True)
79+
def test_debug_exempt():
80+
request = rf.get('/')
81+
response = HttpResponseServerError()
82+
mw.process_response(request, response)
83+
assert HEADER not in response

0 commit comments

Comments
 (0)