Production ready WebDav extension for Django.
Django is a very popular tool which provides data representation and management. One of the key goals is to provide machine access to it. Most popular production ready tools provide json based api access. Which have their own advantages and disadvantages.
WebDav today is a standard for cooperative document management. Its clients are built in the modern operation systems and supported by the world popular services. But it is very important to remember that it's not only about file storage, WebDab provides a set of methods to deal with tree structured objects of any kind.
Providing WebDav access to Django resources opens new horizons for building Web2.0 apps, with inplace edition and providing native operation system access to the stored objects.
An example app is provided at another repository.
For a quick example please look at the code at the bottom of this readme.
- Create a virtual environment:
virtualenv -p python3 env
- Activate virtual environment:
source env/bin/activate
- Install dependencies:
pip install requirements.txt
- Edit Source Code and make a Pull Request :)
- Cleanup
- Django 1.11+ compatibility
- Python3 compatibility
- Replaced vulnerable lxml.etree.parse function with
defusedxml.lxml.parse
- Documentation
- Pep8/Pycodestyle fixes
The original source code is from the following repositories
Base resource functionality was separated into BaseResource class from the storage functionality which developers free to choose from provided or implement themselves.
Improved class dependencies. Resource class don’t know anything about url or server, its goal is only to store content and provide proper access.
Removed properties helper class. View is now responsible for xml generation, and resource provides actual property list.
Server is now inherited from Django Class Based View, and renamed to DavView.
Key methods covered with tests.
Removed redundant request handler.
Added FSResource and DBResource to provide file system and data base access.
Xml library usage is replaced with lxml to achieve proper xml generation code readability.
Be careful when using Basic Authentication on Windows, as it is not enabled by default (for non SSL sites). You can
either set BasicAuthLevel
to 2
in the Windows Registry , or
just make sure your site uses SSL and has a valid SSL certificate.
Windows enforces a 47 MB limit on WebDav files. See this issue on Microsoft Answers
aswell as this issue on StackExchange.
It can be fixed by increasing the registry parameter FileSizeLimitInBytes
in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters
to 4294967295
.
Another way to fix this issue is using a dedicated WebDav client for Windows, such as RaiDrive.
- Install
djangodav
from this repo:pip install git+https://github.com/anx-ckreuzberger/djangodav
- Add
djangodav
andrest_framework
to yourINSTALLED_APPS
:
INSTALLED_APPS = [
...
# djangodav
'djangodav',
# rest_framework neeeds to be here for templates
'rest_framework',
]
This will just host the provided directory /path/to/folder
, without any permission handling.
- Create resources.py
from djangodav.base.resources import MetaEtagMixIn
from djangodav.fs.resources import DummyFSDAVResource
class MyFSDavResource(MetaEtagMixIn, DummyFSDAVResource):
root = '/path/to/folder'
- Register WebDav view in urls.py
from djangodav.acls import FullAcl
from djangodav.locks import DummyLock
from djangodav.views import DavView
from django.conf.urls import patterns
from .resource import MyFSDavResource
# include fsdav/webdav without trailing slash (do not use a slash like in 'fsdav/(?P<path>.*)$')
urlpatterns = patterns('',
(r'^fsdav(?P<path>.*)$', DavView.as_view(resource_class=MyFSDavResource, lock_class=DummyLock,
acl_class=FullAcl)),
)
This example is a bit more complex, as it requires two Django models and some handling.
- Create the following models in models.py
from django.db import models
from django.utils.timezone import now
class BaseWebDavModel(models.Model):
name = models.CharField(max_length=255)
created = models.DateTimeField(default=now)
modified = models.DateTimeField(default=now)
class Meta:
abstract = True
class CollectionModel(BaseWebDavModel):
parent = models.ForeignKey('self', blank=True, null=True)
size = 0
class Meta:
unique_together = (('parent', 'name'),)
def __str__(self):
return "Collection {}".format(self.name)
class ObjectModel(BaseWebDavModel):
parent = models.ForeignKey(CollectionModel, blank=True, null=True)
path = models.FileField(max_length=255)
size = models.IntegerField(default=0)
md5 = models.CharField(max_length=255)
class Meta:
unique_together = (('parent', 'name'),)
def __str__(self):
return "Object {}".format(self.name)
- Create resources.py
from hashlib import md5
from django.conf import settings
from djangodav.db.resources import NameLookupDBDavMixIn, BaseDBDavResource
from .models import CollectionModel, ObjectModel
class MyDBDavResource(NameLookupDBDavMixIn, BaseDBDavResource):
collection_model = CollectionModel
object_model = ObjectModel
root = "/path/to/folder"
def write(self, request, temp_file=None):
size = len(request.body)
# calculate a hashsum of the request (ToDo: probably need to replace this with SHA1 or such, and maybe add a salt)
hashsum = md5(request.body).hexdigest()
# save the file
new_path = os.path.join(settings.MEDIA_ROOT, self.displayname)
f = open(new_path, 'wb')
f.write(request.body)
f.close()
if not self.exists:
obj = self.object_model(
name=self.displayname,
parent=self.get_parent().obj,
md5=hashsum,
size=size
)
obj.path.name = new_path
obj.save()
return
self.obj.size = size
self.obj.modified = now()
self.obj.path.name = new_path
self.obj.md5 = hashsum
self.obj.save(update_fields=['path', 'size', 'modified', 'md5'])
def read(self):
return self.obj.path
@property
def etag(self):
return self.obj.md5
@property
def getcontentlength(self):
return self.obj.size
- Register WebDav view in urls.py
from djangodav.acls import FullAcl
from djangodav.locks import DummyLock
from djangodav.views import DavView
from django.conf.urls import patterns
from .resource import MyDBDavResource
# include fsdav/webdav without trailing slash (do not use a slash like in 'dbdav/(?P<path>.*)$')
urlpatterns = patterns('',
(r'^dbdav(?P<path>.*)$', DavView.as_view(resource_class=MyFSDavResource, lock_class=DummyLock,
acl_class=FullAcl)),
)