Commit fba71aef authored by Serge S. Koval's avatar Serge S. Koval

Added relative_path to upload fields. Fixes #269

parent 99f09f5b
import os
import os.path as op
import logging
from urlparse import urljoin
from flask import url_for
......@@ -86,8 +86,12 @@ class ImageUploadInput(object):
}
if field.data and isinstance(field.data, string_types):
args['image'] = html_params(src=url_for(field.endpoint,
filename=field.thumbnail_fn(field.data)))
if field.thumbnail_size:
url = url_for(field.endpoint, filename=field.thumbnail_fn(field.data))
else:
url = url_for(field.endpoint, filename=field.data)
args['image'] = html_params(src=url)
template = self.data_template
else:
......@@ -107,7 +111,8 @@ class FileUploadField(fields.TextField):
widget = FileUploadInput()
def __init__(self, label=None, validators=None,
path=None, namegen=None, allowed_extensions=None,
base_path=None, relative_path=None,
namegen=None, allowed_extensions=None,
**kwargs):
"""
Constructor.
......@@ -116,8 +121,12 @@ class FileUploadField(fields.TextField):
Display label
:param validators:
Validators
:param path:
Full path to the directory which will store files
:param base_path:
Absolute path to the directory which will store files
:param relative_path:
Relative path from the directory. Will be prepended to the file name for uploaded files.
Flask-Admin uses `urlparse.urljoin` to generate resulting filename, so make sure you have
trailing slash.
:param namegen:
Function that will generate filename from the model and uploaded file object.
Please note, that model is "dirty" model object, before it was committed to database.
......@@ -136,10 +145,12 @@ class FileUploadField(fields.TextField):
:param allowed_extensions:
List of allowed extensions. If not provided, will allow any file.
"""
if not path:
if not base_path:
raise ValueError('FileUploadField field requires target path.')
self.path = path
self.base_path = base_path
self.relative_path = relative_path
self.namegen = namegen or namegen_filename
self.allowed_extensions = allowed_extensions
self._should_delete = False
......@@ -186,19 +197,31 @@ class FileUploadField(fields.TextField):
if field:
self._delete_file(field)
filename = self.namegen(obj, self.data)
filename = self.generate_name(obj, self.data)
self._save_file(self.data, filename)
setattr(obj, name, filename)
def generate_name(self, obj, file_data):
filename = self.namegen(obj, file_data)
if not self.relative_path:
return filename
return urljoin(self.relative_path, filename)
def _get_path(self, filename):
return op.join(self.base_path, filename)
def _delete_file(self, filename):
path = op.join(self.path, filename)
path = self._get_path(filename)
if op.exists(path):
os.remove(path)
def _save_file(self, data, filename):
data.save(op.join(self.path, filename))
path = self._get_path(filename)
data.save(path)
class ImageUploadField(FileUploadField):
......@@ -212,7 +235,8 @@ class ImageUploadField(FileUploadField):
widget = ImageUploadInput()
def __init__(self, label=None, validators=None,
path=None, namegen=None, allowed_extensions=None,
base_path=None, relative_path=None,
namegen=None, allowed_extensions=None,
thumbgen=None, thumbnail_size=None, endpoint='static',
**kwargs):
"""
......@@ -222,8 +246,12 @@ class ImageUploadField(FileUploadField):
Display label
:param validators:
Validators
:param path:
Full path to the directory which will store files
:param base_path:
Absolute path to the directory which will store files
:param relative_path:
Relative path from the directory. Will be prepended to the file name for uploaded files.
Flask-Admin uses `urlparse.urljoin` to generate resulting filename, so make sure you have
trailing slash.
:param namegen:
Function that will generate filename from the model and uploaded file object.
Please note, that model is "dirty" model object, before it was committed to database.
......@@ -277,7 +305,8 @@ class ImageUploadField(FileUploadField):
allowed_extensions = ('gif', 'jpg', 'jpeg', 'png')
super(ImageUploadField, self).__init__(label, validators,
path=path,
base_path=base_path,
relative_path=relative_path,
namegen=namegen,
allowed_extensions=allowed_extensions,
**kwargs)
......@@ -298,14 +327,14 @@ class ImageUploadField(FileUploadField):
self._delete_thumbnail(filename)
def _delete_thumbnail(self, filename):
path = op.join(self.path, self.thumbnail_fn(filename))
path = self._get_path(self.thumbnail_fn(filename))
if op.exists(path):
os.remove(path)
# Saving
def _save_file(self, data, filename):
data.save(op.join(self.path, filename))
data.save(self._get_path(filename))
self._save_thumbnail(data, filename)
......@@ -321,7 +350,7 @@ class ImageUploadField(FileUploadField):
else:
thumb = self.image.copy().thumbnail((width, height), Image.ANTIALIAS)
path = op.join(self.path, self.thumbnail_fn(filename))
path = self._get_path(self.thumbnail_fn(filename))
with open(path, 'wb') as fp:
thumb.save(fp, 'JPEG')
......
......@@ -5,7 +5,7 @@ from io import BytesIO
from nose.tools import eq_, ok_
from flask import Flask
from flask import Flask, url_for
from flask.ext.admin import form, helpers
......@@ -13,6 +13,11 @@ def _create_temp():
path = op.join(op.dirname(__file__), 'tmp')
if not op.exists(path):
os.mkdir(path)
inner = op.join(path, 'inner')
if not op.exists(inner):
os.mkdir(inner)
return path
......@@ -33,13 +38,13 @@ def test_upload_field():
safe_delete(path, 'test2.txt')
class TestForm(form.BaseForm):
upload = form.FileUploadField('Upload', path=path)
upload = form.FileUploadField('Upload', base_path=path)
class Dummy(object):
pass
my_form = TestForm()
eq_(my_form.upload.path, path)
eq_(my_form.upload.base_path, path)
_remove_testfiles()
......@@ -91,16 +96,18 @@ def test_image_upload_field():
safe_delete(path, 'test2_thumb.jpg')
class TestForm(form.BaseForm):
upload = form.ImageUploadField('Upload', path=path, thumbnail_size=(100, 100, True))
upload = form.ImageUploadField('Upload',
base_path=path,
thumbnail_size=(100, 100, True))
class TestNoResizeForm(form.BaseForm):
upload = form.ImageUploadField('Upload', path=path)
upload = form.ImageUploadField('Upload', base_path=path, endpoint='test')
class Dummy(object):
pass
my_form = TestForm()
eq_(my_form.upload.path, path)
eq_(my_form.upload.base_path, path)
eq_(my_form.upload.endpoint, 'static')
_remove_testimages()
......@@ -162,3 +169,39 @@ def test_image_upload_field():
eq_(dummy.upload, 'test1.png')
ok_(op.exists(op.join(path, 'test1.png')))
ok_(not op.exists(op.join(path, 'test1_thumb.jpg')))
def test_relative_path():
app = Flask(__name__)
path = _create_temp()
def _remove_testfiles():
safe_delete(path, 'test1.txt')
class TestForm(form.BaseForm):
upload = form.FileUploadField('Upload', base_path=path, relative_path='inner/')
class Dummy(object):
pass
my_form = TestForm()
eq_(my_form.upload.base_path, path)
eq_(my_form.upload.relative_path, 'inner/')
_remove_testfiles()
dummy = Dummy()
# Check upload
with app.test_request_context(method='POST', data={'upload': (BytesIO(b'Hello World 1'), 'test1.txt')}):
my_form = TestForm(helpers.get_form_data())
ok_(my_form.validate())
my_form.populate_obj(dummy)
eq_(dummy.upload, 'inner/test1.txt')
ok_(op.exists(op.join(path, 'inner/test1.txt')))
eq_(url_for('static', filename=dummy.upload), '/static/inner/test1.txt')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment