[Django]-Django-storages dropbox.stone_validators.ValidationError

0đź‘Ť

âś…

The issue is with the Windows operating system Path. I tried with the same settings in Ubuntu. It’s working there flawlessly.

👤Jhon Doe

3đź‘Ť

The other answers aren’t quite correct about why this error is happening. The dropbox storage object back-end uses a django utility (django.utils._os.safe_join) to verify a filename for the target operating system. See the code here

Even though you pass the upload_to argument which gives a unix-style path (something like /save/path) the django utility makes sure the root of that save path matches the base path for the operating system :(. The dropbox SDK does not like a drive prefixed to the desired save path (AKA C:/path/file.name is not a valid save directory for dropbox).

To make this work on Windows make sure you do a couple things

First, modify the storage back-end _full_path method like this (or create your own subclass).

def _full_path(self, name):
    if name == '/':
        name = ''
    print('Root path in dropbox.storage : ', self.root_path)

    # If the machine is windows do not append the drive letter to file path
    if os.name == 'nt':
        final_path = os.path.join(self.root_path, name).replace('\\', '/')

        # Separator on linux system
        sep = '//'
        base_path = self.root_path

        if (not os.path.normcase(final_path).startswith(os.path.normcase(base_path + sep)) and
                os.path.normcase(final_path) != os.path.normcase(base_path) and
                os.path.dirname(os.path.normcase(base_path)) != os.path.normcase(base_path)):
            raise SuspiciousFileOperation(
                'The joined path ({}) is located outside of the base path '
                'component ({})'.format(final_path, base_path))
        # TODO Testing
        print('Full file path in storage.dropbox._full_path : ', final_path)
        return final_path

    else:
        return safe_join(self.root_path, name).replace('\\', '/')

Second, make sure you are passing the content and name to the model FileField. I had troubles when I did not explicitly pass the file contents and name (something about the uploaded file context retaining the file name?). I re-implemented my models save method like this

def save(self, *args, **kwargs):
    # Save file

    ## Save raw entry from user ##
    # Extract files contents
    try:
        uploaded_raw_entry = kwargs['upload_raw_entry']
    except KeyError:
        raise UploadError(('No file was passed from the admin interface. ' + 
            'Make sure a ContentFile was passed when calling this models save method'))

    # Test raw_directory_path TODO Remove after testing
    print('Raw entry name from model.save : ', raw_directory_path(self, uploaded_raw_entry.name))

    with uploaded_raw_entry.open(mode='rb') as f:
        raw_entry_content = f.read()
    raw_entry_file = ContentFile(content=raw_entry_content.encode('utf-8'),
                                 name=raw_directory_path(self, uploaded_raw_entry.name))

    # Save the content file into a model field
    raw_entry_file.open(mode='rb')
    self.raw_entry.save(raw_entry_file.name, raw_entry_file, save=False)
    raw_entry_file.close()

This definitely is workable from Windows, but with extra steps 🙂

👤J.Vo

2đź‘Ť

This error message is coming from the Dropbox API, indicating that the “path” supplied for the Dropbox API call doesn’t have the expected format. E.g., if you’re uploading a file, the “path” you supply would be the path in the Dropbox account where you want to put the uploaded data.

You’re supplying the value:

D:/media/10506738_10150004552801856_220367501106153455_o.jpg

(That seems to be a local Windows filesystem path.)

The Dropbox path you supply should instead look something like:

/media/10506738_10150004552801856_220367501106153455_o.jpg

(That would be a path relative to the Dropbox root. It doesn’t have a drive letter.)

You’ll need to dig in to your code to see where/why that incorrect type of path is being supplied to Dropbox.

👤Greg

1đź‘Ť

I encountered this issue also on my Linux workstation – so the equivalent Linux answer for @greg’s answer is to add a "/" to your backup name:

import sys, os
import dropbox
from dropbox.files import WriteMode
from dropbox.exceptions import ApiError, AuthError

# Add OAuth2 access token here.
TOKEN = '<your-token>'

data_path = './data_files'
filename   = 'my-file-backup.txt'
BACKUPPATH = "/"+filename  # <----- This should do the trick!

# Uploads contents of filename to Dropbox
def backup():
    current_file_path = os.path.join(data_path, filename)
    with open(current_file_path, 'rb') as f:
        print("Uploading " + filename + " to Dropbox as " + BACKUPPATH + "...")
        try:
            dbx.files_upload(f.read(), BACKUPPATH, mode=WriteMode('overwrite'))
        except ApiError as err:
            if (err.error.is_path() and
                    err.error.get_path().reason.is_insufficient_space()):
                sys.exit("ERROR: Cannot back up; insufficient space.")
            elif err.user_message_text:
                print(err.user_message_text)
                sys.exit()
            else:
                print(err)
                sys.exit()

So, the error message is quiet confusing – the file name and path in question is not your local but the Dropbox remote file name and path.

👤Rotem jackoby

Leave a comment