Commits

Ron DuPlain committed 2ca8c24

Add tests for path folder param.

  • Participants
  • Parent commits e0e0b32

Comments (0)

Files changed (2)

flaskext/uploads.py

     switches uploads from memory to a temporary file when they hit 500 KiB,
     it's still possible for someone to overload your disk space with a
     gigantic file.
-    
+
     This patches the app's request class's
     `~werkzeug.BaseRequest.max_content_length` attribute so that any upload
     larger than the given size is rejected with an HTTP error.
-    
+
     .. note::
-       
+
        In Flask 0.6, you can do this by setting the `MAX_CONTENT_LENGTH`
        setting, without patching the request class. To emulate this behavior,
        you can pass `None` as the size (you must pass it explicitly). That is
        the best way to call this function, as it won't break the Flask 0.6
        functionality if it exists.
-    
+
     .. versionchanged:: 0.1.1
-    
+
     :param app: The app to patch the request class of.
     :param size: The maximum size to accept, in bytes. The default is 64 MiB.
                  If it is `None`, the app's `MAX_CONTENT_LENGTH` configuration
     """
     This is a helper function for `configure_uploads` that extracts the
     configuration for a single set.
-    
+
     :param uset: The upload set.
     :param app: The app to load the configuration from.
     :param defaults: A dict with keys `url` and `dest` from the
     using_defaults = False
     if defaults is None:
         defaults = dict(dest=None, url=None)
-    
+
     allow_extns = tuple(config.get(prefix + 'ALLOW', ()))
     deny_extns = tuple(config.get(prefix + 'DENY', ()))
     destination = config.get(prefix + 'DEST')
     base_url = config.get(prefix + 'URL')
-    
+
     if destination is None:
         # the upload set's destination wasn't given
         if uset.default_dest:
                 destination = os.path.join(defaults['dest'], uset.name)
             else:
                 raise RuntimeError("no destination for set %s" % uset.name)
-    
+
     if base_url is None and using_defaults and defaults['url']:
         base_url = addslash(defaults['url']) + uset.name + '/'
-    
+
     return UploadConfiguration(destination, base_url, allow_extns, deny_extns)
 
 
     upload sets, get their configuration, and store the configuration on the
     app. It will also register the uploads module if it hasn't been set. This
     can be called multiple times with different upload sets.
-    
+
     .. versionchanged:: 0.1.3
        The uploads module/blueprint will only be registered if it is needed
        to serve the upload sets.
-    
+
     :param app: The `~flask.Flask` instance to get the configuration from.
     :param upload_sets: The `UploadSet` instances to configure.
     """
     if isinstance(upload_sets, UploadSet):
         upload_sets = (upload_sets,)
-    
+
     if not hasattr(app, 'upload_set_config'):
         app.upload_set_config = {}
     set_config = app.upload_set_config
     defaults = dict(dest=app.config.get('UPLOADS_DEFAULT_DEST'),
                     url=app.config.get('UPLOADS_DEFAULT_URL'))
-    
+
     for uset in upload_sets:
         config = config_for_set(uset, app, defaults)
         set_config[uset.name] = config
-    
+
     should_serve = any(s.base_url is None for s in set_config.itervalues())
     if using_blueprints:
         if '_uploads' not in app.blueprints and should_serve:
     """
     This can be used to allow all file types except certain ones. For example,
     to ban .exe and .iso files, pass::
-    
+
         AllExcept(('exe', 'iso'))
-    
+
     to the `UploadSet` constructor as `extensions`. You can use any container,
     for example::
-    
+
         AllExcept(SCRIPTS + EXECUTABLES)
     """
     def __init__(self, items):
         self.items = items
-    
+
     def __contains__(self, item):
         return item not in self.items
 
     """
     This holds the configuration for a single `UploadSet`. The constructor's
     arguments are also the attributes.
-    
+
     :param destination: The directory to save files to.
     :param base_url: The URL (ending with a /) that files can be downloaded
                      from. If this is `None`, Flask-Uploads will serve the
         self.base_url = base_url
         self.allow = allow
         self.deny = deny
-    
+
     @property
     def tuple(self):
         return (self.destination, self.base_url, self.allow, self.deny)
-    
+
     def __eq__(self, other):
         return self.tuple == other.tuple
 
     independent of the others. This can be reused across multiple application
     instances, as all configuration is stored on the application object itself
     and found with `flask.current_app`.
-    
+
     :param name: The name of this upload set. It defaults to ``files``, but
                  you can pick any alphanumeric name you want. (For simplicity,
                  it's best to use a plural noun.)
         self.extensions = extensions
         self._config = None
         self.default_dest = default_dest
-    
+
     @property
     def config(self):
         """
             return current_app.upload_set_config[self.name]
         except AttributeError:
             raise RuntimeError("cannot access configuration outside request")
-    
+
     def url(self, filename):
         """
         This function gets the URL a file uploaded to this set would be
         accessed at. It doesn't check whether said file exists.
-        
+
         :param filename: The filename to return the URL for.
         """
         base = self.config.base_url
                            filename=filename, _external=True)
         else:
             return base + filename
-    
+
     def path(self, filename, folder=None):
         """
         This returns the absolute path of a file uploaded to this set. It
         doesn't actually check whether said file exists.
-        
+
         :param filename: The filename to return the path for.
-        :param folder: The subfolder within the upload set previously used 
+        :param folder: The subfolder within the upload set previously used
                        to save to.
         """
-        if folder:
+        if folder is not None:
             target_folder = os.path.join(self.config.destination, folder)
         else:
             target_folder = self.config.destination
         return os.path.join(target_folder, filename)
-    
+
     def file_allowed(self, storage, basename):
         """
         This tells whether a file is allowed. It should return `True` if the
         given `werkzeug.FileStorage` object can be saved with the given
         basename, and `False` if it can't. The default implementation just
         checks the extension, so you can override this if you want.
-        
+
         :param storage: The `werkzeug.FileStorage` to check.
         :param basename: The basename it will be saved under.
         """
         return self.extension_allowed(extension(basename))
-    
+
     def extension_allowed(self, ext):
         """
         This determines whether a specific extension is allowed. It is called
         by `file_allowed`, so if you override that but still want to check
         extensions, call back into this.
-        
+
         :param ext: The extension to check, without the dot.
         """
         return ((ext in self.config.allow) or
                 (ext in self.extensions and ext not in self.config.deny))
-    
+
     def save(self, storage, folder=None, name=None):
         """
         This saves a `werkzeug.FileStorage` into this upload set. If the
         upload is not allowed, an `UploadNotAllowed` error will be raised.
         Otherwise, the file will be saved and its name (including the folder)
         will be returned.
-        
+
         :param storage: The uploaded file to save.
         :param folder: The subfolder within the upload set to save to.
         :param name: The name to save the file as. If it ends with a dot, the
         """
         if not isinstance(storage, FileStorage):
             raise TypeError("storage must be a werkzeug.FileStorage")
-        
+
         if folder is None and name is not None and "/" in name:
             folder, name = name.rsplit("/", 1)
-        
+
         basename = lowercase_ext(secure_filename(storage.filename))
         if name:
             if name.endswith('.'):
                 basename = name + extension(basename)
             else:
                 basename = name
-        
+
         if not self.file_allowed(storage, basename):
             raise UploadNotAllowed()
-        
+
         if folder:
             target_folder = os.path.join(self.config.destination, folder)
         else:
             os.makedirs(target_folder)
         if os.path.exists(os.path.join(target_folder, basename)):
             basename = self.resolve_conflict(target_folder, basename)
-        
+
         target = os.path.join(target_folder, basename)
         storage.save(target)
         if folder:
             return posixpath.join(folder, basename)
         else:
             return basename
-    
+
     def resolve_conflict(self, target_folder, basename):
         """
         If a file with the selected name already exists in the target folder,
         this method is called to resolve the conflict. It should return a new
         basename for the file.
-        
+
         The default implementation splits the name and extension and adds a
         suffix to the name consisting of an underscore and a number, and tries
         that until it finds one that doesn't exist.
-        
+
         :param target_folder: The absolute path to the target.
         :param basename: The file's original basename.
         """
     can manually create it, and its save method is overloaded to set `saved`
     to the name of the file it was saved to. All of these parameters are
     optional, so only bother setting the ones relevant to your application.
-    
+
     :param stream: A stream. The default is an empty stream.
     :param filename: The filename uploaded from the client. The default is the
                      stream's name.
             content_type=content_type, content_length=content_length,
             headers=None)
         self.saved = None
-    
+
     def save(self, dst, buffer_size=16384):
         """
         This marks the file as saved by setting the `saved` attribute to the
         name of the file it was saved to.
-        
+
         :param dst: The file to save to.
         :param buffer_size: Ignored.
         """

tests/test-uploads.py

         assert tfs.saved is None
         tfs.save('foo_bar.txt')
         assert tfs.saved == 'foo_bar.txt'
-    
+
     def test_extension(self):
         assert extension('foo.txt') == 'txt'
         assert extension('foo') == 'foo'
         assert extension('archive.tar.gz') == 'gz'
         assert extension('audio.m4a') == 'm4a'
-    
+
     def test_addslash(self):
         assert (addslash('http://localhost:4000') ==
                 'http://localhost:4000/')
                 'http://localhost:4000/')
         assert (addslash('http://localhost/uploads/') ==
                 'http://localhost/uploads/')
-    
+
     def test_custom_iterables(self):
         assert 'txt' in ALL
         assert 'exe' in ALL
 class TestConfiguration(object):
     def setup(self):
         self.app = Flask(__name__)
-    
+
     def teardown(self):
         del self.app
-    
+
     def configure(self, *sets, **options):
         self.app.config.update(options)
         configure_uploads(self.app, sets)
         return self.app.upload_set_config
-    
+
     def test_manual(self):
         f, p = UploadSet('files'), UploadSet('photos')
         setconfig = self.configure(f, p,
         fconf, pconf = setconfig['files'], setconfig['photos']
         assert fconf == Config('/var/files', 'http://localhost:6001/')
         assert pconf == Config('/mnt/photos', 'http://localhost:6002/')
-    
+
     def test_selfserve(self):
         f, p = UploadSet('files'), UploadSet('photos')
         setconfig = self.configure(f, p,
         fconf, pconf = setconfig['files'], setconfig['photos']
         assert fconf == Config('/var/files', None)
         assert pconf == Config('/mnt/photos', None)
-    
+
     def test_defaults(self):
         f, p = UploadSet('files'), UploadSet('photos')
         setconfig = self.configure(f, p,
                                'http://localhost:6000/files/')
         assert pconf == Config('/var/uploads/photos',
                                'http://localhost:6000/photos/')
-    
+
     def test_default_selfserve(self):
         f, p = UploadSet('files'), UploadSet('photos')
         setconfig = self.configure(f, p,
         fconf, pconf = setconfig['files'], setconfig['photos']
         assert fconf == Config('/var/uploads/files', None)
         assert pconf == Config('/var/uploads/photos', None)
-    
+
     def test_mixed_defaults(self):
         f, p = UploadSet('files'), UploadSet('photos')
         setconfig = self.configure(f, p,
         assert fconf == Config('/var/uploads/files',
                                'http://localhost:6001/files/')
         assert pconf == Config('/mnt/photos', 'http://localhost:6002/')
-    
+
     def test_defaultdest_callable(self):
         f = UploadSet('files', default_dest=lambda app: os.path.join(
             app.config['INSTANCE'], 'files'
         for name, result in namepairs:
             tfs = TestingFileStorage(filename=name)
             assert uset.file_allowed(tfs, name) is result
-    
+
     def test_default_extensions(self):
         uset = UploadSet('files')
         uset._config = Config('/uploads')
     def setup(self):
         self.old_makedirs = os.makedirs
         os.makedirs = lambda v: None
-    
+
     def teardown(self):
         os.makedirs = self.old_makedirs
         del self.old_makedirs
-    
+
     def test_saved(self):
         uset = UploadSet('files')
         uset._config = Config('/uploads')
         res = uset.save(tfs)
         assert res == 'foo.txt'
         assert tfs.saved == '/uploads/foo.txt'
-    
+
     def test_save_folders(self):
         uset = UploadSet('files')
         uset._config = Config('/uploads')
         res = uset.save(tfs, folder='someguy')
         assert res == 'someguy/foo.txt'
         assert tfs.saved == '/uploads/someguy/foo.txt'
-    
+
     def test_save_named(self):
         uset = UploadSet('files')
         uset._config = Config('/uploads')
         res = uset.save(tfs, name='file_123.txt')
         assert res == 'file_123.txt'
         assert tfs.saved == '/uploads/file_123.txt'
-    
+
     def test_save_namedext(self):
         uset = UploadSet('files')
         uset._config = Config('/uploads')
         res = uset.save(tfs, name='photo_123.')
         assert res == 'photo_123.jpg'
         assert tfs.saved == '/uploads/photo_123.jpg'
-    
+
     def test_folder_namedext(self):
         uset = UploadSet('files')
         uset._config = Config('/uploads')
         res = uset.save(tfs, folder='someguy', name='photo_123.')
         assert res == 'someguy/photo_123.jpg'
         assert tfs.saved == '/uploads/someguy/photo_123.jpg'
-    
+
     def test_implicit_folder(self):
         uset = UploadSet('files')
         uset._config = Config('/uploads')
         res = uset.save(tfs, name='someguy/photo_123.')
         assert res == 'someguy/photo_123.jpg'
         assert tfs.saved == '/uploads/someguy/photo_123.jpg'
-    
+
     def test_secured_filename(self):
         uset = UploadSet('files', ALL)
         uset._config = Config('/uploads')
         os.path.exists = self.exists
         self.old_makedirs = os.makedirs
         os.makedirs = lambda v: None
-    
+
     def teardown(self):
         os.path.exists = self.old_exists
         del self.extant_files, self.old_exists
         os.makedirs = self.old_makedirs
         del self.old_makedirs
-    
+
     def extant(self, *files):
         self.extant_files.extend(files)
-    
+
     def exists(self, fname):
         return fname in self.extant_files
-    
+
     def test_self(self):
         assert not os.path.exists('/uploads/foo.txt')
         self.extant('/uploads/foo.txt')
         assert os.path.exists('/uploads/foo.txt')
-    
+
     def test_conflict(self):
         uset = UploadSet('files')
         uset._config = Config('/uploads')
         self.extant('/uploads/foo.txt')
         res = uset.save(tfs)
         assert res == 'foo_1.txt'
-    
+
     def test_multi_conflict(self):
         uset = UploadSet('files')
         uset._config = Config('/uploads')
         uset._config = Config('/uploads')
         assert uset.path('foo.txt') == '/uploads/foo.txt'
         assert uset.path('someguy/foo.txt') == '/uploads/someguy/foo.txt'
-    
+        assert (uset.path('foo.txt', folder='someguy') ==
+                '/uploads/someguy/foo.txt')
+        assert (uset.path('foo/bar.txt', folder='someguy') ==
+                '/uploads/someguy/foo/bar.txt')
+
     def test_url_generated(self):
         app = Flask(__name__)
         app.config.update(
             gen = url_for('_uploads.uploaded_file', setname='files',
                           filename='foo.txt', _external=True)
             assert url == gen
-    
+
     def test_url_based(self):
         app = Flask(__name__)
         app.config.update(