Skip to content

Commit 817c07b

Browse files
committed
add python 3 compatibility
1 parent 5925667 commit 817c07b

17 files changed

+123
-63
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@
2323
*.pot
2424
*.pyc
2525
env
26+
env3
2627
.idea
28+
.vscode
29+
*.sqlite
30+
migrations
31+
__pycache__

.travis.yml

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
language: python
22
python:
33
- "2.7"
4-
- "2.6"
4+
- "3.8"
55
env:
6-
- DJANGO_VERSION=1.6.11
7-
- DJANGO_VERSION=1.7.7
6+
- DJANGO_VERSION=1.11.29
7+
- DJANGO_VERSION=3.1.2
88
matrix:
99
exclude:
10-
- python: "2.6"
11-
env: DJANGO_VERSION=1.7.7
12-
install: pip install -q Django==$DJANGO_VERSION djangorestframework==3.2.5
10+
- python: "2.7"
11+
env: DJANGO_VERSION=1.11.29
12+
- python: "3.8"
13+
env: DJANGO_VERSION=3.1.2
14+
install: pip install -q Django==$DJANGO_VERSION djangorestframework mock lxml
1315
script: python setup.py test

djangodav/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@
1919
# You should have received a copy of the GNU Affero General Public License
2020
# along with DjangoDav. If not, see <http://www.gnu.org/licenses/>.
2121

22-
VERSION = (0, 0, 1, 'beta', 23)
23-
__version__ = "0.0.1b23"
22+
VERSION = (0, 0, 1, 'beta', 24)
23+
__version__ = "0.0.1b24"

djangodav/auth/tests.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
from os.path import dirname
23
from base64 import b64encode
34

@@ -100,7 +101,11 @@ class RestAuthDavView(TestDAVView):
100101
self.assertIsNotAuthorized(response)
101102

102103
# get with basic authentication goes through
103-
request = RequestFactory().get('/', **{'HTTP_AUTHORIZATION': 'Basic %s' % b64encode('root:test')})
104+
if sys.version_info < (3, 0, 0): #py2
105+
b64encode_str = b64encode('root:test')
106+
else:
107+
b64encode_str = b64encode(b'root:test').decode('utf-8')
108+
request = RequestFactory().get('/', **{'HTTP_AUTHORIZATION': 'Basic %s' % b64encode_str})
104109
response = v(request, '/')
105110
self.assertIsAuthorized(response)
106111

@@ -125,6 +130,10 @@ class RestAuthDavView(TestDAVView):
125130
self.assertIsAuthorized(response)
126131

127132
# get with basic authentication goes through
128-
request = RequestFactory().get('/', **{'HTTP_AUTHORIZATION': 'Basic %s' % b64encode('root:test')})
133+
if sys.version_info < (3, 0, 0): #py2
134+
b64encode_str = b64encode('root:test')
135+
else:
136+
b64encode_str = b64encode(b'root:test').decode('utf-8')
137+
request = RequestFactory().get('/', **{'HTTP_AUTHORIZATION': 'Basic %s' % b64encode_str})
129138
response = v(request, '/')
130139
self.assertIsAuthorized(response)

djangodav/base/resources.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from hashlib import md5
2222
from mimetypes import guess_type
2323

24+
from django.utils.encoding import force_bytes
2425
from django.utils.http import urlquote
2526
from djangodav.utils import rfc3339_date, rfc1123_date, safe_join
2627

@@ -191,8 +192,8 @@ def getetag(self):
191192
file system. The etag is used to detect changes to a resource between HTTP calls. So this
192193
needs to change if a resource is modified."""
193194
hashsum = md5()
194-
hashsum.update(self.displayname)
195-
hashsum.update(str(self.creationdate))
196-
hashsum.update(str(self.getlastmodified))
197-
hashsum.update(str(self.getcontentlength))
195+
hashsum.update(force_bytes(self.displayname))
196+
hashsum.update(force_bytes(self.creationdate))
197+
hashsum.update(force_bytes(self.getlastmodified))
198+
hashsum.update(force_bytes(self.getcontentlength))
198199
return hashsum.hexdigest()

djangodav/db/resources.py

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
# You should have received a copy of the GNU Affero General Public License
2020
# along with DjangoDav. If not, see <http://www.gnu.org/licenses/>.
2121
from operator import and_
22+
# from itertools import reduce
2223
from django.core.exceptions import ObjectDoesNotExist
2324
from django.db.models import Q
2425
from django.utils.functional import cached_property

djangodav/fs/resources.py

+11-10
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,15 @@ def exists(self):
8080

8181
def get_children(self):
8282
"""Return an iterator of all direct children of this resource."""
83-
for child in os.listdir(self.get_abs_path()):
84-
try:
85-
is_unicode = isinstance(child, unicode)
86-
except NameError: # Python 3 fix
87-
is_unicode = isinstance(child, str)
88-
if not is_unicode:
89-
child = child.decode(fs_encoding)
90-
yield self.clone(url_join(*(self.path + [child])))
83+
if os.path.isdir(self.get_abs_path()):
84+
for child in os.listdir(self.get_abs_path()):
85+
try:
86+
is_unicode = isinstance(child, unicode)
87+
except NameError: # Python 3 fix
88+
is_unicode = isinstance(child, str)
89+
if not is_unicode:
90+
child = child.decode(fs_encoding)
91+
yield self.clone(url_join(*(self.path + [child])))
9192

9293
def write(self, content):
9394
raise NotImplementedError
@@ -117,13 +118,13 @@ def move_object(self, destination):
117118

118119
class DummyReadFSDavResource(BaseFSDavResource):
119120
def read(self):
120-
with open(self.get_abs_path(), 'r') as f:
121+
with open(self.get_abs_path(), 'rb') as f:
121122
return f.read()
122123

123124

124125
class DummyWriteFSDavResource(BaseFSDavResource):
125126
def write(self, request):
126-
with file(self.get_abs_path(), 'w') as dst:
127+
with open(self.get_abs_path(), 'wb') as dst:
127128
shutil.copyfileobj(request, dst)
128129

129130

djangodav/fs/tests.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ def test_get_size(self, getsize):
5858
def test_get_abs_path(self):
5959
self.assertEquals(self.resource.get_abs_path(), '/some/folder/path/to/name')
6060

61+
@patch('djangodav.fs.resources.os.path.isdir')
6162
@patch('djangodav.fs.resources.os.listdir')
62-
def test_get_children(self, listdir):
63+
def test_get_children(self, listdir, isdir):
6364
listdir.return_value=['child1', 'child2']
6465
children = list(self.resource.get_children())
6566
self.assertEqual(children[0].path, ['path', 'to', 'name', 'child1'])

djangodav/locks.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
# along with DjangoDav. If not, see <http://www.gnu.org/licenses/>.
2121
from uuid import uuid4
2222

23+
from django.utils.encoding import force_text
24+
2325
from djangodav.base.locks import BaseLock
2426

2527

@@ -28,7 +30,7 @@ def get(self, *args, **kwargs):
2830
pass
2931

3032
def acquire(self, *args, **kwargs):
31-
return unicode(uuid4())
33+
return force_text(uuid4())
3234

3335
def release(self, token):
3436
return True

djangodav/utils.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
import datetime, time, calendar
2424
from wsgiref.handlers import format_date_time
25+
26+
from django.utils.encoding import force_text
2527
from django.utils.feedgenerator import rfc2822_date
2628

2729
try:
@@ -61,7 +63,7 @@ def get_property_tag(res, name):
6163
return D(name)
6264
try:
6365
if hasattr(res, name):
64-
return D(name, unicode(getattr(res, name)))
66+
return D(name, force_text(getattr(res, name)))
6567
except AttributeError:
6668
return
6769

djangodav/views/tests.py

+19-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#
1919
# You should have received a copy of the GNU Affero General Public License
2020
# along with DjangoDav. If not, see <http://www.gnu.org/licenses/>.
21+
import sys
2122
from lxml.etree import ElementTree
2223
from django.http import HttpResponse, HttpRequest, Http404
2324
from djangodav.acls import FullAcl
@@ -325,34 +326,46 @@ def test_get_obj(self):
325326
self.assertEqual(resp['Etag'], "0" * 40)
326327
self.assertEqual(resp['Content-Type'], "text/plain")
327328
self.assertEqual(resp['Last-Modified'], "Wed, 24 Dec 2014 06:00:00 +0000")
328-
self.assertEqual(resp.content, "C" * 42)
329+
if sys.version_info < (3, 0, 0): #py2
330+
self.assertEqual(resp.content, "C" * 42)
331+
else:
332+
self.assertEqual(resp.content.decode('utf-8'), "C" * 42)
329333

330-
@patch('djangodav.views.render_to_response', Mock(return_value=HttpResponse('listing')))
334+
@patch('djangodav.views.render', Mock(return_value=HttpResponse('listing')))
331335
def test_head_object(self):
332336
path = '/object.txt'
333337
v = DavView(path=path, base_url='/base', _allowed_methods=Mock(return_value=['ALL']), acl_class=FullAcl)
334338
v.__dict__['resource'] = MockObject(path)
335339
resp = v.head(None, path)
336340
self.assertEqual("text/plain", resp['Content-Type'])
337341
self.assertEqual("Wed, 24 Dec 2014 06:00:00 +0000", resp['Last-Modified'])
338-
self.assertEqual("", resp.content)
342+
if sys.version_info < (3, 0, 0): #py2
343+
self.assertEqual("", resp.content)
344+
else:
345+
self.assertEqual("", resp.content.decode('utf-8'))
339346
self.assertEqual("0", resp['Content-Length'])
340347

341-
@patch('djangodav.views.views.render_to_response', Mock(return_value=HttpResponse('listing')))
348+
@patch('djangodav.views.views.render', Mock(return_value=HttpResponse('listing')))
342349
def test_get_collection(self):
343350
path = '/collection/'
344351
v = DavView(path=path, acl_class=FullAcl, base_url='/base', _allowed_methods=Mock(return_value=['ALL']))
345352
v.__dict__['resource'] = MockCollection(path)
346353
resp = v.get(None, path)
347-
self.assertEqual("listing", resp.content)
354+
if sys.version_info < (3, 0, 0): #py2
355+
self.assertEqual("listing", resp.content)
356+
else:
357+
self.assertEqual("listing", resp.content.decode('utf-8'))
348358
self.assertEqual("Wed, 24 Dec 2014 06:00:00 +0000", resp['Last-Modified'])
349359

350360
def test_head_collection(self):
351361
path = '/collection/'
352362
v = DavView(path=path, acl_class=FullAcl, base_url='/base', _allowed_methods=Mock(return_value=['ALL']))
353363
v.__dict__['resource'] = MockCollection(path)
354364
resp = v.head(None, path)
355-
self.assertEqual("", resp.content)
365+
if sys.version_info < (3, 0, 0): #py2
366+
self.assertEqual("", resp.content)
367+
else:
368+
self.assertEqual("", resp.content.decode('utf-8'))
356369
self.assertEqual("Wed, 24 Dec 2014 06:00:00 +0000", resp['Last-Modified'])
357370
self.assertEqual("0", resp['Content-Length'])
358371

djangodav/views/views.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import urllib, re
2+
import sys
23
try:
34
import urlparse
45
except ImportError:
56
from urllib import parse as urlparse
67
from sys import version_info as python_version
7-
from django.utils.timezone import now
88
from lxml import etree
99

10+
from django.utils.encoding import force_text
11+
from django.utils.timezone import now
1012
from django.http import HttpResponseForbidden, HttpResponseNotAllowed, HttpResponseBadRequest, \
1113
HttpResponseNotModified, HttpResponseRedirect, Http404
1214
from django.utils.decorators import method_decorator
1315
from django.utils.functional import cached_property
1416
from django.utils.http import parse_etags
15-
from django.shortcuts import render_to_response
17+
from django.shortcuts import render
1618
from django.views.decorators.csrf import csrf_exempt
1719
from django.views.generic import View
1820

@@ -192,7 +194,7 @@ def get(self, request, path, head=False, *args, **kwargs):
192194
response['Content-Length'] = self.resource.getcontentlength
193195
response.content = self.resource.read()
194196
elif not head:
195-
response = render_to_response(self.template_name, dict(resource=self.resource, base_url=self.base_url))
197+
response = render(request, self.template_name, dict(resource=self.resource, base_url=self.base_url))
196198
response['Last-Modified'] = self.resource.getlastmodified
197199
return response
198200

@@ -247,7 +249,13 @@ def relocate(self, request, path, method, *args, **kwargs):
247249
raise Http404("Resource doesn't exists")
248250
if not self.has_access(self.resource, 'read'):
249251
return self.no_access()
250-
dst = urlparse.unquote(request.META.get('HTTP_DESTINATION', '')).decode(self.xml_encoding)
252+
# dst = urlparse.unquote(request.META.get('HTTP_DESTINATION', '')).decode(self.xml_encoding)
253+
if sys.version_info < (3, 0, 0): #py2
254+
# in Python 2, urlparse requires bytestrings
255+
dst = urlparse.unquote(request.META.get('HTTP_DESTINATION', '')).decode(self.xml_encoding)
256+
else:
257+
# in Python 3, urlparse understands string
258+
dst = urlparse.unquote(request.META.get('HTTP_DESTINATION', ''))
251259
if not dst:
252260
return HttpResponseBadRequest('Destination header missing.')
253261
dparts = urlparse.urlparse(dst)
@@ -339,7 +347,7 @@ def lock(self, request, path, xbody=None, *args, **kwargs):
339347
body = D.activelock(*([
340348
D.locktype(locktype_obj),
341349
D.lockscope(lockscope_obj),
342-
D.depth(unicode(depth)),
350+
D.depth(force_text(depth)),
343351
D.timeout("Second-%s" % timeout),
344352
D.locktoken(D.href('opaquelocktoken:%s' % token))]
345353
+ ([owner_obj] if owner_obj is not None else [])

runtests.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,14 @@
3737
ENGINE = 'django.db.backends.sqlite3'
3838
)
3939
),
40-
ROOT_URLCONF = 'djangodav.tests.urls',
41-
MIDDLEWARE_CLASSES = ()
40+
# ROOT_URLCONF = 'djangodav.tests.urls',
41+
MIDDLEWARE_CLASSES = (),
42+
TEMPLATES = [
43+
{
44+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
45+
'APP_DIRS': True,
46+
},
47+
]
4248
)
4349

4450

samples/db/models.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@ class Meta:
3333

3434

3535
class CollectionModel(BaseDavModel):
36-
parent = models.ForeignKey('self', blank=True, null=True)
36+
parent = models.ForeignKey('self', blank=True, null=True, on_delete=models.CASCADE)
3737
size = 0
3838

3939
class Meta:
4040
unique_together = (('parent', 'name'),)
4141

4242

4343
class ObjectModel(BaseDavModel):
44-
parent = models.ForeignKey(CollectionModel, blank=True, null=True)
44+
parent = models.ForeignKey(CollectionModel, blank=True, null=True, on_delete=models.CASCADE)
4545
size = models.IntegerField(default=0)
4646
content = models.TextField(default=u"")
4747
md5 = models.CharField(max_length=255)

samples/requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
django
22
django-tastypie
33
djangorestframework
4+
lxml

0 commit comments

Comments
 (0)