Skip to content

Commit cb7ef05

Browse files
Django 1.11 Support, Python 3 compatibility, several bugfixes, updates, docs
1 parent cc3084e commit cb7ef05

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+572
-679
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,10 @@
2222
*.log
2323
*.pot
2424
*.pyc
25+
__pycache__
2526
env
2627
.idea
28+
.eggs
29+
*.egg-info
30+
dist
31+
build

.travis.yml

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
language: python
22
python:
33
- "2.7"
4-
- "2.6"
4+
- "3.4"
5+
- "3.5"
6+
- "3.6"
57
env:
6-
- DJANGO_VERSION=1.6.11
7-
- DJANGO_VERSION=1.7.7
8-
matrix:
9-
exclude:
10-
- python: "2.6"
11-
env: DJANGO_VERSION=1.7.7
12-
install: pip install -q Django==$DJANGO_VERSION djangorestframework==3.2.5
8+
- DJANGO_VERSION=1.11
9+
- DJANGO_VERSION=2.0
10+
install: pip install -q Django==$DJANGO_VERSION djangorestframework>=3.6,<3.8
1311
script: python setup.py test

README.rst

+192-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ DjangoDav
33

44
Production ready WebDav extension for Django.
55

6-
.. image:: https://travis-ci.org/meteozond/djangodav.svg
6+
.. image:: https://travis-ci.org/anx-ckreuzberger/djangodav.svg
77

88
Motivation
99
----------
@@ -13,13 +13,52 @@ machine access to it. Most popular production ready tools provide json based api
1313
advantages and disadvantages.
1414

1515
WebDav today is a standard for cooperative document management. Its clients are built in the modern operation systems
16-
and supported by the world popular services. But it very important to remember that it's not only about file storage,
16+
and supported by the world popular services. But it is very important to remember that it's not only about file storage,
1717
WebDab provides a set of methods to deal with tree structured objects of any kind.
1818

1919
Providing WebDav access to Django resources opens new horizons for building Web2.0 apps, with inplace edition and
2020
providing native operation system access to the stored objects.
2121

2222

23+
Example App
24+
-----------
25+
26+
An example app is provided `at another repository <https://github.com/anx-ckreuzberger/djangodav-example-app>`_.
27+
28+
For a quick example please look at the code at the bottom of this readme.
29+
30+
31+
Development & Contributions
32+
---------------------------
33+
34+
- Create a virtual environment: ``virtualenv -p python3 env``
35+
- Activate virtual environment: ``source env/bin/activate``
36+
- Install dependencies: ``pip install requirements.txt``
37+
- Edit Source Code and make a Pull Request :)
38+
39+
40+
Contributions within this repository
41+
------------------------------------
42+
43+
- Cleanup
44+
- Django 1.11+ compatibility
45+
- Python3 compatibility
46+
- Replaced `vulnerable lxml.etree.parse function <https://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html>`_ with ``defusedxml.lxml.parse``
47+
- Documentation
48+
- Pep8/Pycodestyle fixes
49+
50+
51+
Original Source
52+
---------------
53+
54+
The original source code is from the following repositories
55+
56+
- `djangodav by TZanke <https://github.com/TZanke/djangodav>`_
57+
- `djangodav by MnogoByte <https://github.com/MnogoByte/djangodav>`_
58+
- `django-webdav by sirmmo <https://github.com/sirmmo/django-webdav>`_
59+
60+
61+
2362
Difference with SmartFile django-webdav
2463
---------------------------------------
2564

@@ -43,19 +82,29 @@ Added FSResource and DBResource to provide file system and data base access.
4382
Xml library usage is replaced with lxml to achieve proper xml generation code readability.
4483

4584

46-
How to create simple filesystem webdav resource
47-
-----------------------------------------------
85+
Basic Authentication on Windows
86+
-------------------------------
4887

49-
1. Create resource.py
50-
~~~~~~~~~~~~~~~~~~~~~
88+
Be careful when using Basic Authentication on Windows, as it is not enabled by default (for non SSL sites). You can
89+
either set ``BasicAuthLevel`` to ``2`` in the `Windows Registry <http://www.windowspage.de/tipps/022703.html>`_ , or
90+
just make sure your site uses SSL and has a valid SSL certificate.
91+
92+
93+
Example 1: Create a simple filesystem webdav resource
94+
-----------------------------------------------------
95+
96+
This will just host the provided directory ``/path/to/folder``, without any permission handling.
97+
98+
1. Create resources.py
99+
~~~~~~~~~~~~~~~~~~~~~~
51100

52101
.. code:: python
53102
54103
from django.conf import settings
55104
from djangodav.base.resources import MetaEtagMixIn
56105
from djangodav.fs.resources import DummyFSDAVResource
57106
58-
class MyDavResource(MetaEtagMixIn, DummyFSDAVResource):
107+
class MyFSDavResource(MetaEtagMixIn, DummyFSDAVResource):
59108
root = '/path/to/folder'
60109
61110
@@ -70,9 +119,143 @@ How to create simple filesystem webdav resource
70119
71120
from django.conf.urls import patterns
72121
73-
from .resource import MyDavResource
122+
from .resource import MyFSDavResource
74123
124+
# include fsdav/webdav without trailing slash (do not use a slash like in 'fsdav/(?P<path>.*)$')
75125
urlpatterns = patterns('',
76-
(r'^fsdav(?P<path>.*)$', DavView.as_view(resource_class=MyDavResource, lock_class=DummyLock,
126+
(r'^fsdav(?P<path>.*)$', DavView.as_view(resource_class=MyFSDavResource, lock_class=DummyLock,
77127
acl_class=FullAcl)),
78128
)
129+
130+
131+
Example 2: Create a simple database webdav resource
132+
---------------------------------------------------
133+
134+
This example is a bit more complex, as it requires two Django models and some handling.
135+
136+
1. Create the following models in models.py
137+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
138+
139+
.. code:: python
140+
141+
from django.db import models
142+
from django.utils.timezone import now
143+
144+
145+
class BaseWebDavModel(models.Model):
146+
name = models.CharField(max_length=255)
147+
created = models.DateTimeField(default=now)
148+
modified = models.DateTimeField(default=now)
149+
150+
class Meta:
151+
abstract = True
152+
153+
154+
class CollectionModel(BaseWebDavModel):
155+
parent = models.ForeignKey('self', blank=True, null=True)
156+
size = 0
157+
158+
class Meta:
159+
unique_together = (('parent', 'name'),)
160+
161+
def __str__(self):
162+
return "Collection {}".format(self.name)
163+
164+
165+
class ObjectModel(BaseWebDavModel):
166+
parent = models.ForeignKey(CollectionModel, blank=True, null=True)
167+
path = models.FileField(max_length=255)
168+
size = models.IntegerField(default=0)
169+
md5 = models.CharField(max_length=255)
170+
171+
class Meta:
172+
unique_together = (('parent', 'name'),)
173+
174+
def __str__(self):
175+
return "Object {}".format(self.name)
176+
177+
178+
179+
2. Create resources.py
180+
~~~~~~~~~~~~~~~~~~~~~~
181+
182+
.. code:: python
183+
184+
from hashlib import md5
185+
186+
from django.conf import settings
187+
from djangodav.db.resources import NameLookupDBDavMixIn, BaseDBDavResource
188+
189+
from .models import CollectionModel, ObjectModel
190+
191+
class MyDBDavResource(NameLookupDBDavMixIn, BaseDBDavResource):
192+
collection_model = CollectionModel
193+
object_model = ObjectModel
194+
195+
root = "/path/to/folder"
196+
197+
def write(self, request, temp_file=None):
198+
size = len(request.body)
199+
200+
# calculate a hashsum of the request (ToDo: probably need to replace this with SHA1 or such, and maybe add a salt)
201+
hashsum = md5(request.body).hexdigest()
202+
203+
# save the file
204+
new_path = os.path.join(settings.MEDIA_ROOT, self.displayname)
205+
206+
f = open(new_path, 'wb')
207+
f.write(request.body)
208+
f.close()
209+
210+
if not self.exists:
211+
obj = self.object_model(
212+
name=self.displayname,
213+
parent=self.get_parent().obj,
214+
md5=hashsum,
215+
size=size
216+
)
217+
218+
obj.path.name = new_path
219+
220+
obj.save()
221+
222+
return
223+
224+
self.obj.size = size
225+
self.obj.modified = now()
226+
self.obj.path.name = new_path
227+
self.obj.md5 = hashsum
228+
229+
self.obj.save(update_fields=['path', 'size', 'modified', 'md5'])
230+
231+
def read(self):
232+
return self.obj.path
233+
234+
@property
235+
def etag(self):
236+
return self.obj.md5
237+
238+
@property
239+
def getcontentlength(self):
240+
return self.obj.size
241+
242+
243+
244+
3. Register WebDav view in urls.py
245+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
246+
247+
.. code:: python
248+
249+
from djangodav.acls import FullAcl
250+
from djangodav.locks import DummyLock
251+
from djangodav.views import DavView
252+
253+
from django.conf.urls import patterns
254+
255+
from .resource import MyDBDavResource
256+
257+
# include fsdav/webdav without trailing slash (do not use a slash like in 'dbdav/(?P<path>.*)$')
258+
urlpatterns = patterns('',
259+
(r'^dbdav(?P<path>.*)$', DavView.as_view(resource_class=MyFSDavResource, lock_class=DummyLock,
260+
acl_class=FullAcl)),
261+
)

djangodav/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Refactoring, Django 1.11 compatibility, cleanups, bugfixes (c) 2018 Christian Kreuzberger <ckreuzberger@anexia-it.com>
2+
# All rights reserved.
3+
#
14
# Portions (c) 2014, Alexander Klimenko <alex@erix.ru>
25
# All rights reserved.
36
#

djangodav/acls.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Refactoring, Django 1.11 compatibility, cleanups, bugfixes (c) 2018 Christian Kreuzberger <ckreuzberger@anexia-it.com>
2+
# All rights reserved.
3+
#
14
# Portions (c) 2014, Alexander Klimenko <alex@erix.ru>
25
# All rights reserved.
36
#

djangodav/auth/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Refactoring, Django 1.11 compatibility, cleanups, bugfixes (c) 2018 Christian Kreuzberger <ckreuzberger@anexia-it.com>
2+
# All rights reserved.
3+
#
14
# Portions (c) 2014, Alexander Klimenko <alex@erix.ru>
25
# All rights reserved.
36
#

djangodav/auth/rest.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Refactoring, Django 1.11 compatibility, cleanups, bugfixes (c) 2018 Christian Kreuzberger <ckreuzberger@anexia-it.com>
2+
# All rights reserved.
3+
#
14
# Portions (c) 2014, Alexander Klimenko <alex@erix.ru>
25
# All rights reserved.
36
#

djangodav/auth/tasty.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Refactoring, Django 1.11 compatibility, cleanups, bugfixes (c) 2018 Christian Kreuzberger <ckreuzberger@anexia-it.com>
2+
# All rights reserved.
3+
#
14
# Portions (c) 2014, Alexander Klimenko <alex@erix.ru>
25
# All rights reserved.
36
#

djangodav/base/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Refactoring, Django 1.11 compatibility, cleanups, bugfixes (c) 2018 Christian Kreuzberger <ckreuzberger@anexia-it.com>
2+
# All rights reserved.
3+
#
14
# Portions (c) 2014, Alexander Klimenko <alex@erix.ru>
25
# All rights reserved.
36
#

djangodav/base/locks.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Refactoring, Django 1.11 compatibility, cleanups, bugfixes (c) 2018 Christian Kreuzberger <ckreuzberger@anexia-it.com>
2+
# All rights reserved.
3+
#
14
# Portions (c) 2014, Alexander Klimenko <alex@erix.ru>
25
# All rights reserved.
36
#

djangodav/base/resources.py

+15-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Refactoring, Django 1.11 compatibility, cleanups, bugfixes (c) 2018 Christian Kreuzberger <ckreuzberger@anexia-it.com>
2+
# All rights reserved.
3+
#
14
# Portions (c) 2014, Alexander Klimenko <alex@erix.ru>
25
# All rights reserved.
36
#
@@ -48,6 +51,8 @@ def get_escaped_path(self):
4851

4952
@property
5053
def displayname(self):
54+
if len(self.path) == 0:
55+
return '/'
5156
if not self.path:
5257
return None
5358
return self.path[-1]
@@ -77,6 +82,7 @@ def get_descendants(self, depth=1, include_self=True):
7782

7883
@property
7984
def getcontentlength(self):
85+
# ToDo: This is a property... it should not have a "get" in the name -> rename it
8086
raise NotImplementedError()
8187

8288
@property
@@ -86,6 +92,7 @@ def creationdate(self):
8692

8793
@property
8894
def getlastmodified(self):
95+
# ToDo: This is a property... it should not have a "get" in the name -> rename it
8996
"""Return the modified time as http_date."""
9097
return rfc1123_date(self.get_modified())
9198

@@ -98,7 +105,8 @@ def get_modified(self):
98105
raise NotImplementedError()
99106

100107
@property
101-
def getetag(self):
108+
def etag(self):
109+
# ToDo: This is a property... it should not have a "get" in the name -> rename it
102110
raise NotImplementedError()
103111

104112
def copy(self, destination, depth=-1):
@@ -151,7 +159,7 @@ def clone(self, *args, **kwargs):
151159
def move_object(self, destination):
152160
raise NotImplemented()
153161

154-
def write(self, content):
162+
def write(self, content, temp_file=None):
155163
raise NotImplementedError()
156164

157165
def read(self):
@@ -185,14 +193,14 @@ def create_collection(self):
185193

186194
class MetaEtagMixIn(object):
187195
@property
188-
def getetag(self):
196+
def etag(self):
189197
"""Calculate an etag for this resource. The default implementation uses an md5 sub of the
190198
absolute path modified time and size. Can be overridden if resources are not stored in a
191199
file system. The etag is used to detect changes to a resource between HTTP calls. So this
192200
needs to change if a resource is modified."""
193201
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))
202+
hashsum.update(self.displayname.encode())
203+
hashsum.update(str(self.creationdate).encode())
204+
hashsum.update(str(self.getlastmodified).encode())
205+
hashsum.update(str(self.getcontentlength).encode())
198206
return hashsum.hexdigest()

0 commit comments

Comments
 (0)