diff --git a/AUTHORS b/AUTHORS index 93667e74a..5b15b2014 100644 --- a/AUTHORS +++ b/AUTHORS @@ -22,6 +22,7 @@ By order of apparition, thanks: * Andrei Coman (Azure) * Chris Streeter (S3 with Boto) * Josh Schneier (Fork maintainer, Bugfixes, Py3K) + * Anthony Monthe (Dropbox) Extra thanks to Marty for adding this in Django, you can buy his very interesting book (Pro Django). diff --git a/requirements-tests.txt b/requirements-tests.txt new file mode 100644 index 000000000..e16f4a897 --- /dev/null +++ b/requirements-tests.txt @@ -0,0 +1,5 @@ +Django>=1.6.2 +pytest==2.6.4 +boto>=2.32.0 +dropbox>=3.24 +mock diff --git a/setup.py b/setup.py index d1a9f39a2..3d17442d0 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ from setuptools import setup - import storages @@ -7,6 +6,10 @@ def read(filename): with open(filename) as f: return f.read() +def readlines(filename): + with open(filename) as f: + return f.readlines() + setup( name='django-storages-redux', version=storages.__version__, @@ -31,13 +34,7 @@ def read(filename): 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', ], - tests_require=[ - 'Django>=1.6.2', - 'pytest', - 'mock', - 'boto>=2.32.0', - 'dropbox>=2.2.0' - ], + tests_require=readlines('requirements-tests.txt'), test_suite='tests', zip_safe=False ) diff --git a/storages/backends/dropbox.py b/storages/backends/dropbox.py new file mode 100644 index 000000000..accacd546 --- /dev/null +++ b/storages/backends/dropbox.py @@ -0,0 +1,91 @@ +# Dropbox storage class for Django pluggable storage system. +# Author: Anthony Monthe +# License: BSD +# +# Usage: +# +# Add below to settings.py: +# DROPBOX_OAUTH2_TOKEN = 'YourOauthToken' + +from __future__ import absolute_import + +from datetime import datetime + +from django.core.files.base import File +from django.core.exceptions import ImproperlyConfigured + +from storages.compat import BytesIO, Storage +from storages.utils import setting + +from dropbox.client import DropboxClient + +DATE_FORMAT = '%a, %d %b %Y %X +0000' + + +class DropBoxStorageException(Exception): + pass + + +class DropBoxFile(File): + def __init__(self, name, storage): + self.name = name + self._storage = storage + + def read(self, num_bytes=None): + self._storage._read(self.name, num_bytes=num_bytes) + + def write(self, content): + self._storage._save(self.name, content) + + +class DropBoxStorage(Storage): + """DropBox Storage class for Django pluggable storage system.""" + + def __init__(self, oauth2_access_token=setting('DROPBOX_OAUTH2_TOKEN')): + if oauth2_access_token is None: + raise ImproperlyConfigured("You must configure a token auth at" + "'settings.DROPBOX_OAUTH2_TOKEN'.") + self.client = DropboxClient(oauth2_access_token) + + def delete(self, name): + self.client.file_delete(name) + + def exists(self, name): + response = self.client.search('/', name, file_limit=1) + return bool(response) + + def listdir(self, path): + directories, files = [], [] + metadata = self.client.metadata(path) + for entry in metadata['contents']: + if entry['is_dir']: + directories.append(entry['path']) + else: + files.append(entry['path']) + return directories, files + + def size(self, name): + metadata = self.client.metadata(name) + return metadata['bytes'] + + def modified_time(self, name): + metadata = self.client.metadata(name) + mod_time = datetime.strptime(metadata['modified'], DATE_FORMAT) + return mod_time + + def accessed_time(self, name): + metadata = self.client.metadata(name) + acc_time = datetime.strptime(metadata['client_mtime'], DATE_FORMAT) + return acc_time + + def _open(self, name): + remote_file = DropBoxFile(name, self) + return remote_file + + def _save(self, name, content): + self.client.put_file(name, BytesIO(content)) + return name + + def _read(self, name, num_bytes=None): + data = self.client.get_file(name) + return data.read(num_bytes) diff --git a/tests/test_dropbox.py b/tests/test_dropbox.py index 64fad6d38..f5b4417e6 100644 --- a/tests/test_dropbox.py +++ b/tests/test_dropbox.py @@ -1,5 +1,4 @@ import re -from io import BytesIO from datetime import datetime try: from unittest import mock @@ -7,11 +6,11 @@ import mock from django.test import TestCase -from django.core.files.base import File +from django.core.files.base import File, ContentFile from storages.backends import dropbox -FILE_DATE = datetime(2015, 8, 24, 15, 06, 41) +FILE_DATE = datetime(2015, 8, 24, 15, 6, 41) FILE_FIXTURE = { 'bytes': 4, 'client_mtime': 'Mon, 24 Aug 2015 15:06:41 +0000', @@ -114,10 +113,10 @@ def test_open(self, *args): @mock.patch('dropbox.client.DropboxClient.put_file', return_value='foo') def test_save(self, *args): - self.storage._save('foo', 'bar') + self.storage._save('foo', b'bar') @mock.patch('dropbox.client.DropboxClient.get_file', - return_value=BytesIO('bar')) + return_value=ContentFile('bar')) def test_read(self, *args): content = self.storage._read('foo') self.assertEqual(content, 'bar') @@ -134,10 +133,10 @@ def setUp(self, *args): @mock.patch('dropbox.client.DropboxClient.put_file', return_value='foo') def test_write(self, *args): - self.storage._save('foo', 'bar') + self.storage._save('foo', b'bar') @mock.patch('dropbox.client.DropboxClient.get_file', - return_value=BytesIO('bar')) + return_value=ContentFile('bar')) def test_read(self, *args): content = self.storage._read('foo') self.assertEqual(content, 'bar') diff --git a/tox.ini b/tox.ini index bcbf2bc3f..f14a4e4b8 100644 --- a/tox.ini +++ b/tox.ini @@ -13,4 +13,4 @@ deps = py27: mock==1.0.1 boto>=2.32.0 pytest==2.6.4 - dropbox>=2.2.0 + dropbox>=3.24