Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
flask-admin
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Python-Dev
flask-admin
Commits
d2775fe3
Commit
d2775fe3
authored
Jul 28, 2013
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Image upload field
parent
959e866b
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
229 additions
and
27 deletions
+229
-27
fields.py
flask_admin/contrib/mongoengine/fields.py
+12
-13
upload.py
flask_admin/form/upload.py
+143
-10
copyleft.png
flask_admin/tests/data/copyleft.png
+0
-0
test_form.py
flask_admin/tests/test_form.py
+73
-4
requirements.txt
requirements.txt
+1
-0
No files found.
flask_admin/contrib/mongoengine/fields.py
View file @
d2775fe3
from
flask
import
request
from
werkzeug.datastructures
import
FileStorage
from
wtforms
import
fields
from
wtforms.fields.core
import
_unset_value
...
...
@@ -29,13 +30,13 @@ class MongoFileField(fields.FileField):
def
__init__
(
self
,
label
=
None
,
validators
=
None
,
**
kwargs
):
super
(
MongoFileField
,
self
)
.
__init__
(
label
,
validators
,
**
kwargs
)
self
.
should_delete
=
False
self
.
_
should_delete
=
False
def
process
(
self
,
formdata
,
data
=
_unset_value
):
if
formdata
:
marker
=
'_
%
s-delete'
%
self
.
name
if
marker
in
formdata
:
self
.
should_delete
=
True
self
.
_
should_delete
=
True
return
super
(
MongoFileField
,
self
)
.
process
(
formdata
,
data
)
...
...
@@ -43,21 +44,19 @@ class MongoFileField(fields.FileField):
field
=
getattr
(
obj
,
name
,
None
)
if
field
is
not
None
:
# If field should be deleted, clean it up
if
self
.
should_delete
:
if
self
.
_
should_delete
:
field
.
delete
()
return
data
=
request
.
files
.
get
(
self
.
name
)
if
data
:
if
isinstance
(
self
.
data
,
FileStorage
):
if
not
field
.
grid_id
:
field
.
put
(
data
.
stream
,
filename
=
data
.
filename
,
content_type
=
data
.
content_type
)
func
=
field
.
put
else
:
field
.
replace
(
data
.
stream
,
filename
=
data
.
filename
,
content_type
=
data
.
content_type
)
func
=
field
.
replace
func
(
self
.
data
.
stream
,
filename
=
self
.
data
.
filename
,
content_type
=
self
.
data
.
content_type
)
class
MongoImageField
(
MongoFileField
):
...
...
flask_admin/form/upload.py
View file @
d2775fe3
import
os
import
os.path
as
op
import
logging
from
flask
import
url_for
from
werkzeug
import
secure_filename
from
werkzeug.datastructures
import
FileStorage
...
...
@@ -12,7 +15,19 @@ from wtforms.fields.core import _unset_value
from
flask.ext.admin.babel
import
gettext
__all__
=
[
'FileUploadInput'
,
'FileUploadField'
,
'namefn_keep_filename'
]
from
flask.ext.admin._compat
import
string_types
try
:
from
PIL
import
Image
,
ImageOps
except
ImportError
:
Image
=
None
ImageOps
=
None
__all__
=
[
'FileUploadInput'
,
'FileUploadField'
,
'ImageUploadInput'
,
'ImageUploadField'
,
'namegen_filename'
,
'thumbgen_filename'
]
# Widgets
...
...
@@ -20,19 +35,62 @@ class FileUploadInput(object):
"""
Renders a file input chooser field.
"""
template
=
(
'<input
%(text)
s><input
%(file)
s>'
)
empty_template
=
(
'<input
%(file)
s>'
)
data_template
=
(
'<div>'
' <input
%(text)
s>'
' <input type="checkbox" name="
%(marker)
s">Delete</input>'
'</div>'
'<input
%(file)
s>'
)
def
__call__
(
self
,
field
,
**
kwargs
):
kwargs
.
setdefault
(
'id'
,
field
.
id
)
return
HTMLString
(
self
.
template
%
{
template
=
self
.
data_template
if
field
.
data
else
self
.
empty_template
return
HTMLString
(
template
%
{
'text'
:
html_params
(
type
=
'text'
,
readonly
=
'readonly'
,
value
=
kwargs
.
get
(
'value'
)),
'file'
:
html_params
(
type
=
'file'
,
**
kwargs
)
**
kwargs
),
'marker'
:
'_
%
s-delete'
%
field
.
name
})
class
ImageUploadInput
(
object
):
"""
Renders a file input chooser field.
"""
empty_template
=
(
'<input
%(file)
s>'
)
data_template
=
(
'<div>'
' <img
%(image)
s>'
' <input type="checkbox" name="
%(marker)
s">Delete</input>'
'</div>'
'<input
%(file)
s>'
)
def
__call__
(
self
,
field
,
**
kwargs
):
kwargs
.
setdefault
(
'id'
,
field
.
id
)
args
=
{
'file'
:
html_params
(
type
=
'file'
,
**
kwargs
),
'marker'
:
'_
%
s-delete'
%
field
.
name
}
value
=
kwargs
.
get
(
'value'
)
if
value
and
isinstance
(
value
,
string_types
):
args
[
'image'
]
=
html_params
(
src
=
url_for
(
field
.
endpoint
,
filename
=
field
.
thumnbnail_fn
(
value
)))
template
=
self
.
data_template
else
:
template
=
self
.
empty_template
return
HTMLString
(
template
%
args
)
# Fields
class
FileUploadField
(
fields
.
TextField
):
"""
...
...
@@ -41,14 +99,13 @@ class FileUploadField(fields.TextField):
widget
=
FileUploadInput
()
def
__init__
(
self
,
label
=
None
,
validators
=
None
,
path
=
None
,
name
fn
=
None
,
endpoint
=
'static'
,
allowed_extensions
=
None
,
path
=
None
,
name
gen
=
None
,
allowed_extensions
=
None
,
**
kwargs
):
if
not
path
:
raise
ValueError
(
'FileUploadField field requires target path.'
)
self
.
path
=
path
self
.
namefn
=
namefn
or
namefn_keep_filename
self
.
endpoint
=
endpoint
self
.
namegen
=
namegen
or
namegen_filename
self
.
allowed_extensions
=
allowed_extensions
self
.
_should_delete
=
False
...
...
@@ -85,19 +142,95 @@ class FileUploadField(fields.TextField):
if
field
:
self
.
_delete_file
(
field
)
filename
=
self
.
name
f
n
(
obj
,
self
.
data
)
filename
=
self
.
name
ge
n
(
obj
,
self
.
data
)
self
.
_save_file
(
self
.
data
,
filename
)
setattr
(
obj
,
name
,
filename
)
def
_delete_file
(
self
,
filename
):
path
=
op
.
join
(
self
.
path
,
filename
)
os
.
remove
(
path
)
if
op
.
exists
(
path
):
os
.
remove
(
path
)
def
_save_file
(
self
,
data
,
filename
):
data
.
save
(
op
.
join
(
self
.
path
,
filename
))
class
ImageUploadField
(
FileUploadField
):
widget
=
ImageUploadInput
()
def
__init__
(
self
,
label
=
None
,
validators
=
None
,
path
=
None
,
namegen
=
None
,
allowed_extensions
=
None
,
thumbgen
=
None
,
thumbnail_size
=
None
,
endpoint
=
'static'
,
**
kwargs
):
# Check if PIL is installed
if
Image
is
None
:
raise
Exception
(
'PIL library was not found'
)
self
.
thumbnail_fn
=
thumbgen
or
thumbgen_filename
self
.
thumbnail_size
=
thumbnail_size
self
.
endpoint
=
endpoint
self
.
image
=
None
if
not
allowed_extensions
:
allowed_extensions
=
(
'gif'
,
'jpg'
,
'jpeg'
,
'png'
)
super
(
ImageUploadField
,
self
)
.
__init__
(
label
,
validators
,
path
=
path
,
namegen
=
namegen
,
allowed_extensions
=
allowed_extensions
,
**
kwargs
)
def
pre_validate
(
self
,
form
):
super
(
ImageUploadField
,
self
)
.
pre_validate
(
form
)
if
isinstance
(
self
.
data
,
FileStorage
):
try
:
self
.
image
=
Image
.
open
(
self
.
data
)
except
Exception
as
e
:
raise
ValidationError
(
'Invalid image:
%
s'
%
e
)
# Deletion
def
_delete_file
(
self
,
filename
):
super
(
ImageUploadField
,
self
)
.
_delete_file
(
filename
)
self
.
_delete_thumbnail
(
filename
)
def
_delete_thumbnail
(
self
,
filename
):
path
=
op
.
join
(
self
.
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
))
self
.
_save_thumbnail
(
data
,
filename
)
def
_save_thumbnail
(
self
,
data
,
filename
):
if
self
.
image
and
self
.
thumbnail_size
:
thumb
=
self
.
image
(
width
,
height
,
force
)
=
self
.
thumbnail_size
if
self
.
image
.
size
[
0
]
>
width
or
self
.
image
.
size
[
1
]
>
height
:
if
force
:
thumb
=
ImageOps
.
fit
(
self
.
image
,
(
width
,
height
),
Image
.
ANTIALIAS
)
else
:
thumb
=
self
.
image
.
copy
()
.
thumbnail
((
width
,
height
),
Image
.
ANTIALIAS
)
path
=
op
.
join
(
self
.
path
,
self
.
thumbnail_fn
(
filename
))
with
file
(
path
,
'wb'
)
as
fp
:
thumb
.
save
(
fp
,
'JPEG'
)
# Helpers
def
name
fn_keep
_filename
(
obj
,
file_data
):
def
name
gen
_filename
(
obj
,
file_data
):
return
secure_filename
(
file_data
.
filename
)
def
thumbgen_filename
(
filename
):
name
,
ext
=
op
.
splitext
(
filename
)
return
'
%
s_thumb.jpg'
%
name
flask_admin/tests/data/copyleft.png
0 → 100644
View file @
d2775fe3
6.73 KB
flask_admin/tests/test_form.py
View file @
d2775fe3
...
...
@@ -15,10 +15,9 @@ def _create_temp():
return
path
def
_remove_testfiles
(
path
):
def
safe_delete
(
path
,
name
):
try
:
os
.
remove
(
op
.
join
(
path
,
'test1.txt'
))
os
.
remove
(
op
.
join
(
path
,
'test2.txt'
))
os
.
remove
(
op
.
join
(
path
,
name
))
except
:
pass
...
...
@@ -28,6 +27,10 @@ def test_upload_field():
path
=
_create_temp
()
def
_remove_testfiles
():
safe_delete
(
path
,
'test1.txt'
)
safe_delete
(
path
,
'test2.txt'
)
class
TestForm
(
form
.
BaseForm
):
upload
=
form
.
FileUploadField
(
'Upload'
,
path
=
path
)
...
...
@@ -37,7 +40,7 @@ def test_upload_field():
my_form
=
TestForm
()
eq_
(
my_form
.
upload
.
path
,
path
)
_remove_testfiles
(
path
)
_remove_testfiles
()
dummy
=
Dummy
()
...
...
@@ -72,3 +75,69 @@ def test_upload_field():
my_form
.
populate_obj
(
dummy
)
ok_
(
not
op
.
exists
(
op
.
join
(
path
,
'test2.txt'
)))
def
test_image_upload_field
():
app
=
Flask
(
__name__
)
path
=
_create_temp
()
def
_remove_testimages
():
safe_delete
(
path
,
'test1.png'
)
safe_delete
(
path
,
'test1_thumb.jpg'
)
safe_delete
(
path
,
'test2.png'
)
safe_delete
(
path
,
'test2_thumb.jpg'
)
class
TestForm
(
form
.
BaseForm
):
upload
=
form
.
ImageUploadField
(
'Upload'
,
path
=
path
,
thumbnail_size
=
(
100
,
100
,
True
))
class
Dummy
(
object
):
pass
my_form
=
TestForm
()
eq_
(
my_form
.
upload
.
path
,
path
)
eq_
(
my_form
.
upload
.
endpoint
,
'static'
)
_remove_testimages
()
dummy
=
Dummy
()
# Check upload
with
file
(
op
.
join
(
op
.
dirname
(
__file__
),
'data'
,
'copyleft.png'
),
'rb'
)
as
fp
:
with
app
.
test_request_context
(
method
=
'POST'
,
data
=
{
'upload'
:
(
fp
,
'test1.png'
)}):
my_form
=
TestForm
(
helpers
.
get_form_data
())
ok_
(
my_form
.
validate
())
my_form
.
populate_obj
(
dummy
)
eq_
(
dummy
.
upload
,
'test1.png'
)
ok_
(
op
.
exists
(
op
.
join
(
path
,
'test1.png'
)))
ok_
(
op
.
exists
(
op
.
join
(
path
,
'test1_thumb.jpg'
)))
# Check replace
with
file
(
op
.
join
(
op
.
dirname
(
__file__
),
'data'
,
'copyleft.png'
),
'rb'
)
as
fp
:
with
app
.
test_request_context
(
method
=
'POST'
,
data
=
{
'upload'
:
(
fp
,
'test2.png'
)}):
my_form
=
TestForm
(
helpers
.
get_form_data
())
ok_
(
my_form
.
validate
())
my_form
.
populate_obj
(
dummy
)
eq_
(
dummy
.
upload
,
'test2.png'
)
ok_
(
op
.
exists
(
op
.
join
(
path
,
'test2.png'
)))
ok_
(
op
.
exists
(
op
.
join
(
path
,
'test2_thumb.jpg'
)))
ok_
(
not
op
.
exists
(
op
.
join
(
path
,
'test1.png'
)))
ok_
(
not
op
.
exists
(
op
.
join
(
path
,
'test1_thumb.jpg'
)))
# Check delete
with
app
.
test_request_context
(
method
=
'POST'
,
data
=
{
'_upload-delete'
:
'checked'
}):
my_form
=
TestForm
(
helpers
.
get_form_data
())
ok_
(
my_form
.
validate
())
my_form
.
populate_obj
(
dummy
)
ok_
(
not
op
.
exists
(
op
.
join
(
path
,
'test2.png'
)))
ok_
(
not
op
.
exists
(
op
.
join
(
path
,
'test2_thumb.jpg'
)))
requirements.txt
View file @
d2775fe3
...
...
@@ -4,3 +4,4 @@ Flask-SQLAlchemy>=0.15
peewee
wtf-peewee
flask-mongoengine
pillow
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment