Skip to content

Commit

Permalink
Merge pull request #13 from maxicecilia/master
Browse files Browse the repository at this point in the history
Migrate to Dropbox API v2
  • Loading branch information
andres-torres-marroquin authored May 28, 2017
2 parents 9b06bb2 + ee0021b commit ef8d5ea
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 96 deletions.
33 changes: 24 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# django-dropbox
> Version 0.0.1
> Version 0.1.2
# What

Expand All @@ -23,16 +23,31 @@ INSTALLED_APPS on settings.py

additionally you must need to set the next settings:

DROPBOX_CONSUMER_KEY = 'xxx'
DROPBOX_CONSUMER_SECRET = 'xxx'
DROPBOX_ACCESS_TOKEN = 'xxx'
DROPBOX_ACCESS_TOKEN_SECRET = 'xxx'

if you don't have `DROPBOX_CONSUMER_KEY` or `DROPBOX_CONSUMER_SECRET`
you will need to create an Dropbox app at [Dropbox for Developers](https://www.dropbox.com/developers)
then set `DROPBOX_CONSUMER_KEY` and `DROPBOX_CONSUMER_SECRET` settings in `settings.py`,
after that run:
if you don't have `DROPBOX_ACCESS_TOKEN` you can create one after creating a Dropbox app at [Dropbox for Developers](https://www.dropbox.com/developers).
If you have your Dropbox `App key` and `App secret`, you can set `DROPBOX_CONSUMER_KEY` and `DROPBOX_CONSUMER_SECRET` settings in `settings.py`, then run:

$ python manage.py get_dropbox_token

And follow up on screen instructions, finally set the `DROPBOX_ACCESS_TOKEN` and `DROPBOX_ACCESS_TOKEN_SECRET` in `settings.py`
And follow up on screen instructions, finally set the and `DROPBOX_ACCESS_TOKEN_SECRET` in `settings.py`


# Contributing
When contributing, please follow these steps:

* Clone the repo and make your changes.
* Make sure your code has test cases written against it.
* Make sure all the tests pass.
* Lint your code with Flake8.
* Add your name to the list of contributers.
* Submit a Pull Request.

## Tests

Tests are written following Django best practices. You can run them all easily using the example django_project.

```
$ cd django_dropbox_project
$ python manage.py test --settings=settings
```
30 changes: 16 additions & 14 deletions django_dropbox/management/commands/get_dropbox_token.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
from django.core.management.base import NoArgsCommand
from dropbox import rest, session
from django_dropbox.settings import CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TYPE
from django.core.management.base import NoArgsCommand, CommandError
from dropbox import DropboxOAuth2FlowNoRedirect

from django_dropbox.settings import CONSUMER_KEY, CONSUMER_SECRET


class Command(NoArgsCommand):

def handle_noargs(self, *args, **options):
sess = session.DropboxSession(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TYPE)
request_token = sess.obtain_request_token()
auth_flow = DropboxOAuth2FlowNoRedirect(CONSUMER_KEY, CONSUMER_SECRET)

url = sess.build_authorize_url(request_token)
print "Url:", url
print "Please visit this website and press the 'Allow' button, then hit 'Enter' here."
raw_input()

# This will fail if the user didn't visit the above URL and hit 'Allow'
access_token = sess.obtain_access_token(request_token)
authorize_url = auth_flow.start()
self.stdout.write('1. Go to: {}'.format(authorize_url))
self.stdout.write('2. Click "Allow" (you might have to log in first).')
self.stdout.write('3. Copy the authorization code.')
auth_code = raw_input("Enter the authorization code here: ").strip()

print "DROPBOX_ACCESS_TOKEN = '%s'" % access_token.key
print "DROPBOX_ACCESS_TOKEN_SECRET = '%s'" % access_token.secret
try:
oauth_result = auth_flow.finish(auth_code)
self.stdout.write("DROPBOX_ACCESS_TOKEN = '{}'".format(oauth_result.access_token))
except Exception as e:
raise CommandError('Error: {}'.format(e))
4 changes: 2 additions & 2 deletions django_dropbox/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
ACCESS_TOKEN = getattr(settings, 'DROPBOX_ACCESS_TOKEN', None)
ACCESS_TOKEN_SECRET = getattr(settings, 'DROPBOX_ACCESS_TOKEN_SECRET', None)
CACHE_TIMEOUT = getattr(settings, 'DROPBOX_CACHE_TIMEOUT', 3600 * 24 * 365) # One year
SHARE_LINK_CACHE_TIMEOUT = getattr(settings, 'DROPBOX_SHARE_LINK_CACHE_TIMEOUT', 3600 * 3)

# ACCESS_TYPE should be 'dropbox' or 'app_folder' as configured for your app
ACCESS_TYPE = getattr(settings,'ACCESS_TYPE','app_folder')

ACCESS_TYPE = getattr(settings, 'ACCESS_TYPE', 'app_folder')
77 changes: 34 additions & 43 deletions django_dropbox/storage.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,33 @@
import errno
import os.path
import re
import urlparse
import urllib
import itertools
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
from dropbox.session import DropboxSession
from dropbox.client import DropboxClient
from dropbox.rest import ErrorResponse
from dropbox import Dropbox
from dropbox.exceptions import ApiError
from dropbox.files import FolderMetadata, FileMetadata
from django.core.cache import cache
from django.core.files import File
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructible
from django.utils.encoding import filepath_to_uri

from .settings import (CONSUMER_KEY,
CONSUMER_SECRET,
ACCESS_TYPE,
ACCESS_TOKEN,
ACCESS_TOKEN_SECRET,
CACHE_TIMEOUT)
from .settings import ACCESS_TOKEN, CACHE_TIMEOUT, SHARE_LINK_CACHE_TIMEOUT


@deconstructible
class DropboxStorage(Storage):
"""
A storage class providing access to resources in a Dropbox Public folder.
"""

def __init__(self, location='/Public'):
session = DropboxSession(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TYPE, locale=None)
session.set_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
self.client = DropboxClient(session)
self.account_info = self.client.account_info()
self.client = Dropbox(ACCESS_TOKEN)
self.account_info = self.client.users_get_current_account()
self.location = location
self.base_url = 'http://dl.dropbox.com/u/{uid}/'.format(**self.account_info)
self.base_url = 'https://dl.dropboxusercontent.com/'

def _get_abs_path(self, name):
return os.path.realpath(os.path.join(self.location, name))
Expand All @@ -49,59 +41,56 @@ def _save(self, name, content):
name = self._get_abs_path(name)
directory = os.path.dirname(name)
if not self.exists(directory) and directory:
self.client.file_create_folder(directory)
response = self.client.metadata(directory)
if not response['is_dir']:
raise IOError("%s exists and is not a directory." % directory)
self.client.files_create_folder(directory)
# response = self.client.files_get_metadata(directory)
# if not response['is_dir']:
# raise IOError("%s exists and is not a directory." % directory)
abs_name = os.path.realpath(os.path.join(self.location, name))
self.client.put_file(abs_name, content)
foo = self.client.files_upload(content.read(), abs_name)
return name

def delete(self, name):
name = self._get_abs_path(name)
self.client.file_delete(name)
self.client.files_delete(name)

def exists(self, name):
name = self._get_abs_path(name)
try:
metadata = self.client.metadata(name)
if metadata.get('is_deleted'):
return False
except ErrorResponse as e:
if e.status == 404: # not found
self.client.files_get_metadata(name)
except ApiError as e:
if e.error.is_path() and e.error.get_path().is_not_found(): # not found
return False
raise e
return True

def listdir(self, path):
path = self._get_abs_path(path)
response = self.client.metadata(path)
response = self.client.files_list_folder(path)
directories = []
files = []
for entry in response.get('contents', []):
if entry['is_dir']:
directories.append(os.path.basename(entry['path']))
else:
files.append(os.path.basename(entry['path']))
for entry in response.entries:
if type(entry) == FolderMetadata:
directories.append(os.path.basename(entry.path_display))
elif type(entry) == FileMetadata:
files.append(os.path.basename(entry.path_display))
return directories, files

def size(self, name):
cache_key = 'django-dropbox-size:%s' % filepath_to_uri(name)
cache_key = 'django-dropbox-size:{}'.format(filepath_to_uri(name))
size = cache.get(cache_key)

if not size:
size = self.client.metadata(filepath_to_uri(name))['bytes']
size = self.client.files_get_metadata(name).size
cache.set(cache_key, size, CACHE_TIMEOUT)

return size

def url(self, name):
cache_key = 'django-dropbox-url:%s' % filepath_to_uri(name)
cache_key = 'django-dropbox-size:{}'.format(filepath_to_uri(name))
url = cache.get(cache_key)

if not url:
url = self.client.share(filepath_to_uri(name), short_url=False)['url'] + '?dl=1'
cache.set(cache_key, url, CACHE_TIMEOUT)
url = self.client.files_get_temporary_link(name).link
cache.set(cache_key, url, SHARE_LINK_CACHE_TIMEOUT)

return url

Expand All @@ -123,6 +112,7 @@ def get_available_name(self, name):

return name


class DropboxFile(File):
def __init__(self, name, storage, mode):
self._storage = storage
Expand All @@ -139,7 +129,8 @@ def size(self):
return self._size

def read(self, num_bytes=None):
return self._storage.client.get_file(self._name).read()
metadata, response = self._storage.client.files_download(self._name)
return response.content

def write(self, content):
if 'w' not in self._mode:
Expand All @@ -149,5 +140,5 @@ def write(self, content):

def close(self):
if self._is_dirty:
self._storage.client.put_file(self._name, self.file.getvalue())
self.file.close()
self._storage.client.files_upload(self.file.getvalue(), self._name)
self.file.close()
7 changes: 4 additions & 3 deletions django_dropbox/tests.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#import os
# import os
from django.core.files.base import ContentFile
from django.test import TestCase
from django_dropbox.storage import DropboxStorage


class DropboxStorageTest(TestCase):

def setUp(self):
Expand All @@ -29,7 +30,7 @@ def test_file_access_options(self):

def test_exists_folder(self):
self.assertFalse(self.storage.exists('storage_test_exists'))
self.storage.client.file_create_folder(self.location + '/storage_test_exists')
self.storage.client.files_create_folder(self.location + '/storage_test_exists')
self.assertTrue(self.storage.exists('storage_test_exists'))
self.storage.delete('storage_test_exists')
self.assertFalse(self.storage.exists('storage_test_exists'))
Expand All @@ -44,7 +45,7 @@ def test_listdir(self):

f = self.storage.save('storage_test_1', ContentFile('custom content'))
f = self.storage.save('storage_test_2', ContentFile('custom content'))
self.storage.client.file_create_folder(self.location + '/storage_dir_1')
self.storage.client.files_create_folder(self.location + '/storage_dir_1')

dirs, files = self.storage.listdir(self.location)
self.assertEqual(set(dirs), set([u'storage_dir_1']))
Expand Down
5 changes: 3 additions & 2 deletions django_dropbox_project/dropbox_testing/admin.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from django.contrib import admin
from dropbox_testing.models import Person


class PersonAdmin(admin.ModelAdmin):
list_display = ('image',)

def image(self, obj):
if obj.photo:
return '<img src="%s">' % obj.photo.url
return ''
image.allow_tags = True

admin.site.register(Person, PersonAdmin)
admin.site.register(Person, PersonAdmin)
5 changes: 3 additions & 2 deletions django_dropbox_project/dropbox_testing/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

STORAGE = DropboxStorage()


class Person(models.Model):
photo = models.ImageField(upload_to='photos', storage=STORAGE, null=True, blank=True)
resume = models.FileField(upload_to='resumes', storage=STORAGE, null=True, blank=True)
photo = models.ImageField(upload_to='photos', storage=STORAGE, null=True, blank=True)
resume = models.FileField(upload_to='resumes', storage=STORAGE, null=True, blank=True)
15 changes: 5 additions & 10 deletions django_dropbox_project/manage.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
#!/usr/bin/env python
from django.core.management import execute_manager
import imp
try:
imp.find_module('settings') # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
sys.exit(1)
import os
import sys

import settings

if __name__ == "__main__":
execute_manager(settings)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
3 changes: 2 additions & 1 deletion django_dropbox_project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,5 @@
try:
from local_settings import *
except ImportError:
raise ImportError('You must add local_settings.py file')
pass
# raise ImportError('You must add local_settings.py file')
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dropbox>=2.0.0
dropbox>=7.3.1
18 changes: 9 additions & 9 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django_dropbox import version
from setuptools import setup


def get_packages():
# setuptools can't do the job :(
packages = []
Expand All @@ -12,14 +13,13 @@ def get_packages():

return packages

requires = ['dropbox>=2.0.0']
requires = ['dropbox==7.3.1']

setup(name='django-dropbox',
version=version,
description='A Django App that contains a Django Storage which uses Dropbox.',
author=u'Andres Torres Marroquin',
author_email='[email protected]',
url='https://github.com/andres-torres-marroquin/django-dropbox',
packages=get_packages(),
install_requires=requires,
)
version=version,
description='A Django App that contains a Django Storage which uses Dropbox.',
author=u'Andres Torres Marroquin',
author_email='[email protected]',
url='https://github.com/andres-torres-marroquin/django-dropbox',
packages=get_packages(),
install_requires=requires,)

0 comments on commit ef8d5ea

Please sign in to comment.