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
1b36345c
Commit
1b36345c
authored
Mar 01, 2015
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #800 from pawl/improvefileadmin
FileAdmin Improvements
parents
3c2b7122
8d690ae9
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
461 additions
and
215 deletions
+461
-215
admin.pot
babel/admin.pot
+99
-98
fileadmin.py
flask_admin/contrib/fileadmin.py
+231
-98
helpers.py
flask_admin/helpers.py
+9
-4
base.py
flask_admin/model/base.py
+5
-7
list.html
flask_admin/templates/bootstrap2/admin/file/list.html
+4
-2
list.html
flask_admin/templates/bootstrap3/admin/file/list.html
+4
-2
test_fileadmin.py
flask_admin/tests/fileadmin/test_fileadmin.py
+109
-4
No files found.
babel/admin.pot
View file @
1b36345c
...
...
@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Flask-Admin VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-0
1-16 12:12
-0600\n"
"POT-Creation-Date: 2015-0
2-28 21:53
-0600\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
...
...
@@ -18,153 +18,159 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
#: ../flask_admin/base.py:4
19
#: ../flask_admin/base.py:4
26
msgid "Home"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:34
msgid "Invalid directory name"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:42
#: ../flask_admin/contrib/fileadmin.py:220
msgid "File to upload"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
51
#: ../flask_admin/contrib/fileadmin.py:
228
msgid "File required."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
56
#: ../flask_admin/contrib/fileadmin.py:
233
msgid "Invalid file type."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
60
#: ../flask_admin/contrib/fileadmin.py:
244
msgid "Content"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:427
#: ../flask_admin/contrib/fileadmin.py:258
msgid "Invalid name"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:266
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:35
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:35
msgid "Name"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:544
#, python-format
msgid "File \"%(name)s\" already exists."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
446
#: ../flask_admin/contrib/fileadmin.py:
512
#: ../flask_admin/contrib/fileadmin.py:
565
#: ../flask_admin/contrib/fileadmin.py:
602
#: ../flask_admin/contrib/fileadmin.py:
64
5
#: ../flask_admin/contrib/fileadmin.py:
693
#: ../flask_admin/contrib/fileadmin.py:
568
#: ../flask_admin/contrib/fileadmin.py:
635
#: ../flask_admin/contrib/fileadmin.py:
688
#: ../flask_admin/contrib/fileadmin.py:
729
#: ../flask_admin/contrib/fileadmin.py:
77
5
#: ../flask_admin/contrib/fileadmin.py:
824
msgid "Permission denied."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
508
#: ../flask_admin/contrib/fileadmin.py:
631
msgid "File uploading is disabled."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
521
#: ../flask_admin/contrib/fileadmin.py:
644
#, python-format
msgid "Failed to save file: %(error)s"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
561
#: ../flask_admin/contrib/fileadmin.py:
684
msgid "Directory creation is disabled."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
576
#: ../flask_admin/contrib/fileadmin.py:
699
#, python-format
msgid "Failed to create directory: %(error)s"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
598
#: ../flask_admin/contrib/fileadmin.py:
725
msgid "Deletion is disabled."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
607
#: ../flask_admin/contrib/fileadmin.py:
734
msgid "Directory deletion is disabled."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
613
#: ../flask_admin/contrib/fileadmin.py:
740
#, python-format
msgid "Directory \"%(path)s\" was successfully deleted."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
615
#: ../flask_admin/contrib/fileadmin.py:
742
#, python-format
msgid "Failed to delete directory: %(error)s"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
620
#: ../flask_admin/contrib/fileadmin.py:
759
#: ../flask_admin/contrib/fileadmin.py:
747
#: ../flask_admin/contrib/fileadmin.py:
892
#, python-format
msgid "File \"%(name)s\" was successfully deleted."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
622
#: ../flask_admin/contrib/fileadmin.py:
761
#: ../flask_admin/contrib/fileadmin.py:
749
#: ../flask_admin/contrib/fileadmin.py:
894
#, python-format
msgid "Failed to delete file: %(name)s"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
64
1
#: ../flask_admin/contrib/fileadmin.py:
77
1
msgid "Renaming is disabled."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
64
9
#: ../flask_admin/contrib/fileadmin.py:
77
9
msgid "Path does not exist."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
661
#: ../flask_admin/contrib/fileadmin.py:
790
#, python-format
msgid "Successfully renamed \"%(src)s\" to \"%(dst)s\""
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
664
#: ../flask_admin/contrib/fileadmin.py:
793
#, python-format
msgid "Failed to rename: %(error)s"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
709
#: ../flask_admin/contrib/fileadmin.py:
840
#, python-format
msgid "Error saving changes to %(name)s."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
713
#: ../flask_admin/contrib/fileadmin.py:
844
#, python-format
msgid "Changes to %(name)s saved successfully."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
720
#: ../flask_admin/contrib/fileadmin.py:
853
#, python-format
msgid "Error reading %(name)s."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
723
#: ../flask_admin/contrib/fileadmin.py:
732
#: ../flask_admin/contrib/fileadmin.py:
856
#: ../flask_admin/contrib/fileadmin.py:
865
#, python-format
msgid "Unexpected error while reading from %(name)s"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
729
#: ../flask_admin/contrib/fileadmin.py:
862
#, python-format
msgid "Cannot edit %(name)s."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
746
#: ../flask_admin/contrib/mongoengine/view.py:6
12
#: ../flask_admin/contrib/peewee/view.py:4
18
#: ../flask_admin/contrib/pymongo/view.py:34
3
#: ../flask_admin/contrib/sqla/view.py:9
45
#: ../flask_admin/contrib/fileadmin.py:
879
#: ../flask_admin/contrib/mongoengine/view.py:6
25
#: ../flask_admin/contrib/peewee/view.py:4
29
#: ../flask_admin/contrib/pymongo/view.py:34
8
#: ../flask_admin/contrib/sqla/view.py:9
74
msgid "Delete"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
747
#: ../flask_admin/contrib/fileadmin.py:
880
msgid "Are you sure you want to delete these files?"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
750
#: ../flask_admin/contrib/fileadmin.py:
883
msgid "File deletion is disabled."
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:
763
#: ../flask_admin/contrib/fileadmin.py:
896
msgid "Edit"
msgstr ""
...
...
@@ -246,57 +252,57 @@ msgstr ""
msgid "not between"
msgstr ""
#: ../flask_admin/contrib/mongoengine/view.py:5
06
#: ../flask_admin/contrib/mongoengine/view.py:5
19
#, python-format
msgid "Failed to get model. %(error)s"
msgstr ""
#: ../flask_admin/contrib/mongoengine/view.py:5
25
#: ../flask_admin/contrib/peewee/view.py:3
69
#: ../flask_admin/contrib/pymongo/view.py:2
78
#: ../flask_admin/contrib/sqla/view.py:
877
#: ../flask_admin/contrib/mongoengine/view.py:5
38
#: ../flask_admin/contrib/peewee/view.py:3
80
#: ../flask_admin/contrib/pymongo/view.py:2
83
#: ../flask_admin/contrib/sqla/view.py:
906
#, python-format
msgid "Failed to create record. %(error)s"
msgstr ""
#: ../flask_admin/contrib/mongoengine/view.py:5
51
#: ../flask_admin/contrib/peewee/view.py:3
88
#: ../flask_admin/contrib/pymongo/view.py:30
3
#: ../flask_admin/contrib/sqla/view.py:9
03 ../flask_admin/model/base.py:156
3
#: ../flask_admin/model/base.py:1
57
2
#: ../flask_admin/contrib/mongoengine/view.py:5
64
#: ../flask_admin/contrib/peewee/view.py:3
99
#: ../flask_admin/contrib/pymongo/view.py:30
8
#: ../flask_admin/contrib/sqla/view.py:9
32 ../flask_admin/model/base.py:161
3
#: ../flask_admin/model/base.py:1
62
2
#, python-format
msgid "Failed to update record. %(error)s"
msgstr ""
#: ../flask_admin/contrib/mongoengine/view.py:5
75
#: ../flask_admin/contrib/peewee/view.py:4
04
#: ../flask_admin/contrib/pymongo/view.py:3
29
#: ../flask_admin/contrib/sqla/view.py:9
29 ../flask_admin/model/base.py:1513
#: ../flask_admin/contrib/mongoengine/view.py:5
88
#: ../flask_admin/contrib/peewee/view.py:4
15
#: ../flask_admin/contrib/pymongo/view.py:3
34
#: ../flask_admin/contrib/sqla/view.py:9
58
#, python-format
msgid "Failed to delete record. %(error)s"
msgstr ""
#: ../flask_admin/contrib/mongoengine/view.py:6
13
#: ../flask_admin/contrib/peewee/view.py:4
19
#: ../flask_admin/contrib/pymongo/view.py:34
4
#: ../flask_admin/contrib/sqla/view.py:9
46
#: ../flask_admin/contrib/mongoengine/view.py:6
26
#: ../flask_admin/contrib/peewee/view.py:4
30
#: ../flask_admin/contrib/pymongo/view.py:34
9
#: ../flask_admin/contrib/sqla/view.py:9
75
msgid "Are you sure you want to delete selected records?"
msgstr ""
#: ../flask_admin/contrib/mongoengine/view.py:6
22
#: ../flask_admin/contrib/peewee/view.py:4
35
#: ../flask_admin/contrib/pymongo/view.py:35
4
#: ../flask_admin/contrib/sqla/view.py:9
62 ../flask_admin/model/base.py:1506
#: ../flask_admin/contrib/mongoengine/view.py:6
35
#: ../flask_admin/contrib/peewee/view.py:4
46
#: ../flask_admin/contrib/pymongo/view.py:35
9
#: ../flask_admin/contrib/sqla/view.py:9
91 ../flask_admin/model/base.py:1561
#, python-format
msgid "Record was successfully deleted."
msgid_plural "%(count)s records were successfully deleted."
msgstr[0] ""
msgstr[1] ""
#: ../flask_admin/contrib/mongoengine/view.py:6
28
#: ../flask_admin/contrib/peewee/view.py:4
41
#: ../flask_admin/contrib/pymongo/view.py:3
59
#: ../flask_admin/contrib/sqla/view.py:9
70
#: ../flask_admin/contrib/mongoengine/view.py:6
41
#: ../flask_admin/contrib/peewee/view.py:4
52
#: ../flask_admin/contrib/pymongo/view.py:3
64
#: ../flask_admin/contrib/sqla/view.py:9
99
#, python-format
msgid "Failed to delete records. %(error)s"
msgstr ""
...
...
@@ -319,7 +325,7 @@ msgid_plural "At least %d items are required"
msgstr[0] ""
msgstr[1] ""
#: ../flask_admin/contrib/sqla/view.py:8
56
#: ../flask_admin/contrib/sqla/view.py:8
85
#, python-format
msgid "Integrity error. %(message)s"
msgstr ""
...
...
@@ -336,20 +342,20 @@ msgstr ""
msgid "Invalid file extension"
msgstr ""
#: ../flask_admin/model/base.py:1
173
#: ../flask_admin/model/base.py:1
227
msgid "There are no items in the table."
msgstr ""
#: ../flask_admin/model/base.py:1
197
#: ../flask_admin/model/base.py:1
251
#, python-format
msgid "Invalid Filter Value: %(value)s"
msgstr ""
#: ../flask_admin/model/base.py:14
30
#: ../flask_admin/model/base.py:14
84
msgid "Record was successfully created."
msgstr ""
#: ../flask_admin/model/base.py:1
467 ../flask_admin/model/base.py:156
8
#: ../flask_admin/model/base.py:1
521 ../flask_admin/model/base.py:161
8
msgid "Record was successfully saved."
msgstr ""
...
...
@@ -392,40 +398,35 @@ msgstr ""
msgid "Root"
msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:35
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:35
msgid "Name"
msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:36
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:36
msgid "Size"
msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:6
2
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:6
2
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:6
3
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:6
3
#, python-format
msgid "Are you sure you want to delete \\'%(name)s\\' recursively?"
msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:7
0
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:7
0
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:7
2
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:7
2
#, python-format
msgid "Are you sure you want to delete \\'%(name)s\\'?"
msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:10
5
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:10
5
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:10
7
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:10
7
msgid "Upload File"
msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:11
0
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:11
0
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:11
2
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:11
2
msgid "Create Directory"
msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:12
7
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:12
7
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:12
9
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:12
9
msgid "Please select at least one file."
msgstr ""
...
...
@@ -519,17 +520,17 @@ msgstr ""
msgid "Edit record"
msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/model/list.html:11
6
#: ../flask_admin/templates/bootstrap3/admin/model/list.html:11
6
#: ../flask_admin/templates/bootstrap2/admin/model/list.html:11
4
#: ../flask_admin/templates/bootstrap3/admin/model/list.html:11
4
msgid "Are you sure you want to delete this record?"
msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/model/list.html:11
6
#: ../flask_admin/templates/bootstrap2/admin/model/list.html:11
4
msgid "Delete record"
msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/model/list.html:1
61
#: ../flask_admin/templates/bootstrap3/admin/model/list.html:1
60
#: ../flask_admin/templates/bootstrap2/admin/model/list.html:1
59
#: ../flask_admin/templates/bootstrap3/admin/model/list.html:1
58
msgid "Please select at least one record."
msgstr ""
flask_admin/contrib/fileadmin.py
View file @
1b36345c
...
...
@@ -19,48 +19,6 @@ from flask.ext.admin.actions import action, ActionsMixin
from
flask.ext.admin.babel
import
gettext
,
lazy_gettext
class
NameForm
(
form
.
BaseForm
):
"""
Form with a filename input field.
Validates if provided name is valid for *nix and Windows systems.
"""
name
=
fields
.
StringField
()
regexp
=
re
.
compile
(
r'^(?!^(PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d|\..*)(\..+)?$)[^\x00-\x1f\\?*:\";|/]+$'
)
def
validate_name
(
self
,
field
):
if
not
self
.
regexp
.
match
(
field
.
data
):
raise
validators
.
ValidationError
(
gettext
(
'Invalid directory name'
))
class
UploadForm
(
form
.
BaseForm
):
"""
File upload form. Works with FileAdmin instance to check if it is allowed
to upload file with given extension.
"""
upload
=
fields
.
FileField
(
lazy_gettext
(
'File to upload'
))
def
__init__
(
self
,
admin
):
self
.
admin
=
admin
super
(
UploadForm
,
self
)
.
__init__
(
helpers
.
get_form_data
())
def
validate_upload
(
self
,
field
):
if
not
self
.
upload
.
data
:
raise
validators
.
ValidationError
(
gettext
(
'File required.'
))
filename
=
self
.
upload
.
data
.
filename
if
not
self
.
admin
.
is_file_allowed
(
filename
):
raise
validators
.
ValidationError
(
gettext
(
'Invalid file type.'
))
class
EditForm
(
form
.
BaseForm
):
content
=
fields
.
TextAreaField
(
lazy_gettext
(
'Content'
),
(
validators
.
required
(),))
class
FileAdmin
(
BaseView
,
ActionsMixin
):
"""
Simple file-management interface.
...
...
@@ -74,11 +32,15 @@ class FileAdmin(BaseView, ActionsMixin):
Sample usage::
import os.path as op
from flask.ext.admin import Admin
from flask.ext.admin.contrib.fileadmin import FileAdmin
admin = Admin()
path = op.join(op.dirname(__file__), 'static')
admin.add_view(FileAdmin(path, '/static/', name='Static Files'))
admin.setup_app(app)
"""
can_upload
=
True
...
...
@@ -156,9 +118,22 @@ class FileAdmin(BaseView, ActionsMixin):
Edit template
"""
upload_form
=
Upload
Form
form_base_class
=
form
.
Base
Form
"""
Upload form class
Base form class. Will be used to create the upload, rename, edit, and delete form.
Allows enabling CSRF validation and useful if you want to have custom
contructor or override some fields.
Example::
class MyBaseForm(Form):
def do_something(self):
pass
class MyAdmin(FileAdmin):
form_base_class = MyBaseForm
"""
def
__init__
(
self
,
base_path
,
base_url
=
None
,
...
...
@@ -231,6 +206,139 @@ class FileAdmin(BaseView, ActionsMixin):
"""
return
self
.
base_url
def
get_upload_form
(
self
):
"""
Upload form class for file upload view.
Override to implement customized behavior.
"""
class
UploadForm
(
self
.
form_base_class
):
"""
File upload form. Works with FileAdmin instance to check if it
is allowed to upload file with given extension.
"""
upload
=
fields
.
FileField
(
lazy_gettext
(
'File to upload'
))
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
UploadForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
admin
=
kwargs
[
'admin'
]
def
validate_upload
(
self
,
field
):
if
not
self
.
upload
.
data
:
raise
validators
.
ValidationError
(
gettext
(
'File required.'
))
filename
=
self
.
upload
.
data
.
filename
if
not
self
.
admin
.
is_file_allowed
(
filename
):
raise
validators
.
ValidationError
(
gettext
(
'Invalid file type.'
))
return
UploadForm
def
get_edit_form
(
self
):
"""
Create form class for file editing view.
Override to implement customized behavior.
"""
class
EditForm
(
self
.
form_base_class
):
content
=
fields
.
TextAreaField
(
lazy_gettext
(
'Content'
),
(
validators
.
required
(),))
return
EditForm
def
get_name_form
(
self
):
"""
Create form class for renaming and mkdir views.
Override to implement customized behavior.
"""
def
validate_name
(
self
,
field
):
regexp
=
re
.
compile
(
r'^(?!^(PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d|\..*)(\..+)?$)[^\x00-\x1f\\?*:\";|/]+$'
)
if
not
regexp
.
match
(
field
.
data
):
raise
validators
.
ValidationError
(
gettext
(
'Invalid name'
))
class
NameForm
(
self
.
form_base_class
):
"""
Form with a filename input field.
Validates if provided name is valid for *nix and Windows systems.
"""
name
=
fields
.
StringField
(
lazy_gettext
(
'Name'
),
validators
=
[
validators
.
Required
(),
validate_name
])
path
=
fields
.
HiddenField
()
return
NameForm
def
get_delete_form
(
self
):
"""
Create form class for model delete view.
Override to implement customized behavior.
"""
class
DeleteForm
(
self
.
form_base_class
):
path
=
fields
.
HiddenField
(
validators
=
[
validators
.
Required
()])
return
DeleteForm
def
upload_form
(
self
):
"""
Instantiate file upload form and return it.
Override to implement custom behavior.
"""
upload_form_class
=
self
.
get_upload_form
()
if
request
.
form
:
# Workaround for allowing both CSRF token + FileField to be submitted
# https://bitbucket.org/danjac/flask-wtf/issue/12/fieldlist-filefield-does-not-follow
formdata
=
request
.
form
.
copy
()
# as request.form is immutable
formdata
.
update
(
request
.
files
)
# admin=self allows the form to use self.is_file_allowed
return
upload_form_class
(
formdata
,
admin
=
self
)
elif
request
.
files
:
return
upload_form_class
(
request
.
files
,
admin
=
self
)
else
:
return
upload_form_class
(
admin
=
self
)
def
name_form
(
self
):
"""
Instantiate form used in rename and mkdir then return it.
Override to implement custom behavior.
"""
name_form_class
=
self
.
get_name_form
()
if
request
.
form
:
return
name_form_class
(
request
.
form
)
elif
request
.
args
:
return
name_form_class
(
request
.
args
)
else
:
return
name_form_class
()
def
edit_form
(
self
):
"""
Instantiate file editing form and return it.
Override to implement custom behavior.
"""
edit_form_class
=
self
.
get_edit_form
()
if
request
.
form
:
return
edit_form_class
(
request
.
form
)
else
:
return
edit_form_class
()
def
delete_form
(
self
):
"""
Instantiate file delete form and return it.
Override to implement custom behavior.
"""
delete_form_class
=
self
.
get_delete_form
()
if
request
.
form
:
return
delete_form_class
(
request
.
form
)
else
:
return
delete_form_class
()
def
is_file_allowed
(
self
,
filename
):
"""
Verify if file can be uploaded.
...
...
@@ -291,6 +399,15 @@ class FileAdmin(BaseView, ActionsMixin):
"""
file_data
.
save
(
path
)
def
validate_form
(
self
,
form
):
"""
Validate the form on submit.
:param form:
Form to validate
"""
return
helpers
.
validate_form_on_submit
(
form
)
def
_get_dir_url
(
self
,
endpoint
,
path
=
None
,
**
kwargs
):
"""
Return prettified URL
...
...
@@ -439,11 +556,16 @@ class FileAdmin(BaseView, ActionsMixin):
:param path:
Optional directory path. If not provided, will use the base directory
"""
if
self
.
can_delete
:
delete_form
=
self
.
delete_form
()
else
:
delete_form
=
None
# Get path and verify if it is valid
base_path
,
directory
,
path
=
self
.
_normalize_path
(
path
)
if
not
self
.
is_accessible_path
(
path
):
flash
(
gettext
(
'Permission denied.'
))
flash
(
gettext
(
'Permission denied.'
)
,
'error'
)
return
redirect
(
self
.
_get_dir_url
(
'.index'
))
# Get directory listing
...
...
@@ -490,7 +612,8 @@ class FileAdmin(BaseView, ActionsMixin):
get_file_url
=
self
.
_get_file_url
,
items
=
items
,
actions
=
actions
,
actions_confirmation
=
actions_confirmation
)
actions_confirmation
=
actions_confirmation
,
delete_form
=
delete_form
)
@
expose
(
'/upload/'
,
methods
=
(
'GET'
,
'POST'
))
@
expose
(
'/upload/<path:path>'
,
methods
=
(
'GET'
,
'POST'
))
...
...
@@ -509,16 +632,16 @@ class FileAdmin(BaseView, ActionsMixin):
return
redirect
(
self
.
_get_dir_url
(
'.index'
,
path
))
if
not
self
.
is_accessible_path
(
path
):
flash
(
gettext
(
'Permission denied.'
))
flash
(
gettext
(
'Permission denied.'
)
,
'error'
)
return
redirect
(
self
.
_get_dir_url
(
'.index'
))
form
=
self
.
upload_form
(
self
)
if
helpers
.
validate_form_on_submit
(
form
):
form
=
self
.
upload_form
()
if
self
.
validate_form
(
form
):
try
:
self
.
_save_form_files
(
directory
,
path
,
form
)
return
redirect
(
self
.
_get_dir_url
(
'.index'
,
path
))
except
Exception
as
ex
:
flash
(
gettext
(
'Failed to save file:
%(error)
s'
,
error
=
ex
))
flash
(
gettext
(
'Failed to save file:
%(error)
s'
,
error
=
ex
)
,
'error'
)
return
self
.
render
(
self
.
upload_template
,
form
=
form
)
...
...
@@ -562,18 +685,20 @@ class FileAdmin(BaseView, ActionsMixin):
return
redirect
(
dir_url
)
if
not
self
.
is_accessible_path
(
path
):
flash
(
gettext
(
'Permission denied.'
))
flash
(
gettext
(
'Permission denied.'
)
,
'error'
)
return
redirect
(
self
.
_get_dir_url
(
'.index'
))
form
=
NameForm
(
helpers
.
get_form_data
()
)
form
=
self
.
name_form
(
)
if
helpers
.
validate_form_on_submit
(
form
):
if
self
.
validate_form
(
form
):
try
:
os
.
mkdir
(
op
.
join
(
directory
,
form
.
name
.
data
))
self
.
on_mkdir
(
directory
,
form
.
name
.
data
)
return
redirect
(
dir_url
)
except
Exception
as
ex
:
flash
(
gettext
(
'Failed to create directory:
%(error)
s'
,
error
=
ex
),
'error'
)
else
:
helpers
.
flash_errors
(
form
,
message
=
'Failed to create directory:
%(error)
s'
)
return
self
.
render
(
self
.
mkdir_template
,
form
=
form
,
...
...
@@ -584,42 +709,46 @@ class FileAdmin(BaseView, ActionsMixin):
"""
Delete view method
"""
path
=
request
.
form
.
get
(
'path'
)
if
not
path
:
return
redirect
(
self
.
get_url
(
'.index'
))
form
=
self
.
delete_form
()
# Get path and verify if it is valid
base_path
,
full_path
,
path
=
self
.
_normalize_path
(
path
)
path
=
form
.
path
.
data
if
path
:
return_url
=
self
.
_get_dir_url
(
'.index'
,
op
.
dirname
(
path
))
else
:
return_url
=
self
.
get_url
(
'.index'
)
return_url
=
self
.
_get_dir_url
(
'.index'
,
op
.
dirname
(
path
))
if
self
.
validate_form
(
form
):
# Get path and verify if it is valid
base_path
,
full_path
,
path
=
self
.
_normalize_path
(
path
)
if
not
self
.
can_delete
:
flash
(
gettext
(
'Deletion is disabled.'
)
)
return
redirect
(
return_url
)
if
not
self
.
can_delete
:
flash
(
gettext
(
'Deletion is disabled.'
),
'error'
)
return
redirect
(
return_url
)
if
not
self
.
is_accessible_path
(
path
):
flash
(
gettext
(
'Permission denied.'
)
)
return
redirect
(
self
.
_get_dir_url
(
'.index'
))
if
not
self
.
is_accessible_path
(
path
):
flash
(
gettext
(
'Permission denied.'
),
'error'
)
return
redirect
(
self
.
_get_dir_url
(
'.index'
))
if
op
.
isdir
(
full_path
):
if
not
self
.
can_delete_dirs
:
flash
(
gettext
(
'Directory deletion is disabled.'
)
)
return
redirect
(
return_url
)
if
op
.
isdir
(
full_path
):
if
not
self
.
can_delete_dirs
:
flash
(
gettext
(
'Directory deletion is disabled.'
),
'error'
)
return
redirect
(
return_url
)
try
:
shutil
.
rmtree
(
full_path
)
self
.
on_directory_delete
(
full_path
,
path
)
flash
(
gettext
(
'Directory "
%(path)
s" was successfully deleted.'
,
path
=
path
))
except
Exception
as
ex
:
flash
(
gettext
(
'Failed to delete directory:
%(error)
s'
,
error
=
ex
),
'error'
)
try
:
shutil
.
rmtree
(
full_path
)
self
.
on_directory_delete
(
full_path
,
path
)
flash
(
gettext
(
'Directory "
%(path)
s" was successfully deleted.'
,
path
=
path
))
except
Exception
as
ex
:
flash
(
gettext
(
'Failed to delete directory:
%(error)
s'
,
error
=
ex
),
'error'
)
else
:
try
:
os
.
remove
(
full_path
)
self
.
on_file_delete
(
full_path
,
path
)
flash
(
gettext
(
'File "
%(name)
s" was successfully deleted.'
,
name
=
path
))
except
Exception
as
ex
:
flash
(
gettext
(
'Failed to delete file:
%(name)
s'
,
name
=
ex
),
'error'
)
else
:
try
:
os
.
remove
(
full_path
)
self
.
on_file_delete
(
full_path
,
path
)
flash
(
gettext
(
'File "
%(name)
s" was successfully deleted.'
,
name
=
path
))
except
Exception
as
ex
:
flash
(
gettext
(
'Failed to delete file:
%(name)
s'
,
name
=
ex
),
'error'
)
helpers
.
flash_errors
(
form
,
message
=
'Failed to delete file.
%(error)
s'
)
return
redirect
(
return_url
)
...
...
@@ -628,29 +757,29 @@ class FileAdmin(BaseView, ActionsMixin):
"""
Rename view method
"""
path
=
request
.
args
.
get
(
'path'
)
if
not
path
:
return
redirect
(
self
.
get_url
(
'.index'
))
form
=
self
.
name_form
()
base_path
,
full_path
,
path
=
self
.
_normalize_path
(
path
)
path
=
form
.
path
.
data
if
path
:
base_path
,
full_path
,
path
=
self
.
_normalize_path
(
path
)
return_url
=
self
.
_get_dir_url
(
'.index'
,
op
.
dirname
(
path
))
return_url
=
self
.
_get_dir_url
(
'.index'
,
op
.
dirname
(
path
))
else
:
return
redirect
(
self
.
get_url
(
'.index'
))
if
not
self
.
can_rename
:
flash
(
gettext
(
'Renaming is disabled.'
))
flash
(
gettext
(
'Renaming is disabled.'
)
,
'error'
)
return
redirect
(
return_url
)
if
not
self
.
is_accessible_path
(
path
):
flash
(
gettext
(
'Permission denied.'
))
flash
(
gettext
(
'Permission denied.'
)
,
'error'
)
return
redirect
(
self
.
_get_dir_url
(
'.index'
))
if
not
op
.
exists
(
full_path
):
flash
(
gettext
(
'Path does not exist.'
))
flash
(
gettext
(
'Path does not exist.'
)
,
'error'
)
return
redirect
(
return_url
)
form
=
NameForm
(
helpers
.
get_form_data
(),
name
=
op
.
basename
(
path
))
if
helpers
.
validate_form_on_submit
(
form
):
if
self
.
validate_form
(
form
):
try
:
dir_base
=
op
.
dirname
(
full_path
)
filename
=
secure_filename
(
form
.
name
.
data
)
...
...
@@ -664,6 +793,8 @@ class FileAdmin(BaseView, ActionsMixin):
flash
(
gettext
(
'Failed to rename:
%(error)
s'
,
error
=
ex
),
'error'
)
return
redirect
(
return_url
)
else
:
helpers
.
flash_errors
(
form
,
message
=
'Failed to rename:
%(error)
s'
)
return
self
.
render
(
self
.
rename_template
,
form
=
form
,
...
...
@@ -690,16 +821,16 @@ class FileAdmin(BaseView, ActionsMixin):
base_path
,
full_path
,
path
=
self
.
_normalize_path
(
path
)
if
not
self
.
is_accessible_path
(
path
)
or
not
self
.
is_file_editable
(
path
):
flash
(
gettext
(
'Permission denied.'
))
flash
(
gettext
(
'Permission denied.'
)
,
'error'
)
return
redirect
(
self
.
_get_dir_url
(
'.index'
))
dir_url
=
self
.
_get_dir_url
(
'.index'
,
os
.
path
.
dirname
(
path
))
next_url
=
next_url
or
dir_url
form
=
EditForm
(
helpers
.
get_form_data
()
)
form
=
self
.
edit_form
(
)
error
=
False
if
helpers
.
validate_form_on_submit
(
form
):
if
self
.
validate_form
(
form
):
form
.
process
(
request
.
form
,
content
=
''
)
if
form
.
validate
():
try
:
...
...
@@ -713,8 +844,10 @@ class FileAdmin(BaseView, ActionsMixin):
flash
(
gettext
(
"Changes to
%(name)
s saved successfully."
,
name
=
path
))
return
redirect
(
next_url
)
else
:
helpers
.
flash_errors
(
form
,
message
=
'Failed to edit file.
%(error)
s'
)
try
:
with
open
(
full_path
,
'r'
)
as
f
:
with
open
(
full_path
,
'r
b
'
)
as
f
:
content
=
f
.
read
()
except
IOError
:
flash
(
gettext
(
"Error reading
%(name)
s."
,
name
=
path
),
'error'
)
...
...
flask_admin/helpers.py
View file @
1b36345c
from
re
import
sub
from
jinja2
import
contextfunction
from
flask
import
g
,
request
,
url_for
from
flask
import
g
,
request
,
url_for
,
flash
from
wtforms.validators
import
DataRequired
,
InputRequired
from
flask.ext.admin._compat
import
urljoin
,
urlparse
from
flask.ext.admin._compat
import
urljoin
,
urlparse
,
iteritems
from
flask.ext.admin.babel
import
gettext
from
._compat
import
string_types
...
...
@@ -93,7 +93,12 @@ def is_field_error(errors):
return
True
return
False
def
flash_errors
(
form
,
message
):
for
field_name
,
errors
in
iteritems
(
form
.
errors
):
errors
=
form
[
field_name
]
.
label
.
text
+
u": "
+
u", "
.
join
(
errors
)
flash
(
gettext
(
message
,
error
=
str
(
errors
)),
'error'
)
@
contextfunction
def
resolve_ctx
(
context
):
...
...
flask_admin/model/base.py
View file @
1b36345c
...
...
@@ -14,7 +14,7 @@ from flask.ext.admin.form import BaseForm, FormOpts, rules
from
flask.ext.admin.model
import
filters
,
typefmt
from
flask.ext.admin.actions
import
ActionsMixin
from
flask.ext.admin.helpers
import
(
get_form_data
,
validate_form_on_submit
,
get_redirect_target
)
get_redirect_target
,
flash_errors
)
from
flask.ext.admin.tools
import
rec_getattr
from
flask.ext.admin._backwards
import
ObsoleteAttr
from
flask.ext.admin._compat
import
iteritems
,
OrderedDict
,
as_unicode
...
...
@@ -972,6 +972,9 @@ class BaseModelView(BaseView, ActionsMixin):
Instantiate model delete form and return it.
Override to implement custom behavior.
The delete form originally used a GET request, so delete_form
accepts both GET and POST request for backwards compatibility.
"""
if
request
.
form
:
return
self
.
_delete_form_class
(
request
.
form
)
...
...
@@ -1558,12 +1561,7 @@ class BaseModelView(BaseView, ActionsMixin):
flash
(
gettext
(
'Record was successfully deleted.'
))
return
redirect
(
return_url
)
else
:
# flash validation errors
for
field_name
,
errors
in
iteritems
(
form
.
errors
):
errors
=
field_name
+
u": "
+
u", "
.
join
(
errors
)
flash
(
gettext
(
'Failed to delete record.
%(error)
s'
,
error
=
str
(
errors
)),
'error'
)
flash_errors
(
form
,
message
=
'Failed to delete record.
%(error)
s'
)
return
redirect
(
return_url
)
...
...
flask_admin/templates/bootstrap2/admin/file/list.html
View file @
1b36345c
...
...
@@ -58,7 +58,8 @@
{% if is_dir %}
{% if name != '..' and admin_view.can_delete_dirs %}
<form
class=
"icon"
method=
"POST"
action=
"{{ get_url('.delete') }}"
>
<input
type=
"hidden"
name=
"path"
value=
"{{ path }}"
></input>
{{ delete_form.path(value=path) }}
{{ delete_form.csrf_token }}
<button
onclick=
"return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\' recursively?', name=name) }}')"
>
<i
class=
"icon-remove"
></i>
</button>
...
...
@@ -66,7 +67,8 @@
{% endif %}
{% else %}
<form
class=
"icon"
method=
"POST"
action=
"{{ get_url('.delete') }}"
>
<input
type=
"hidden"
name=
"path"
value=
"{{ path }}"
></input>
{{ delete_form.path(value=path) }}
{{ delete_form.csrf_token }}
<button
onclick=
"return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\'?', name=name) }}')"
>
<i
class=
"icon-remove"
></i>
</button>
...
...
flask_admin/templates/bootstrap3/admin/file/list.html
View file @
1b36345c
...
...
@@ -58,7 +58,8 @@
{% if is_dir %}
{% if name != '..' and admin_view.can_delete_dirs %}
<form
class=
"icon"
method=
"POST"
action=
"{{ get_url('.delete') }}"
>
<input
type=
"hidden"
name=
"path"
value=
"{{ path }}"
></input>
{{ delete_form.path(value=path) }}
{{ delete_form.csrf_token }}
<button
onclick=
"return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\' recursively?', name=name) }}')"
>
<i
class=
"glyphicon glyphicon-remove"
></i>
</button>
...
...
@@ -66,7 +67,8 @@
{% endif %}
{% else %}
<form
class=
"icon"
method=
"POST"
action=
"{{ get_url('.delete') }}"
>
<input
type=
"hidden"
name=
"path"
value=
"{{ path }}"
></input>
{{ delete_form.path(value=path) }}
{{ delete_form.csrf_token }}
<button
onclick=
"return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\'?', name=name) }}')"
>
<i
class=
"glyphicon glyphicon-trash"
></i>
</button>
...
...
flask_admin/tests/fileadmin/test_fileadmin.py
View file @
1b36345c
from
nose.tools
import
eq_
,
ok_
import
os.path
as
op
from
nose.tools
import
eq_
,
ok_
from
flask.ext.admin.contrib
import
fileadmin
from
.
import
setup
try
:
from
StringIO
import
StringIO
except
ImportError
:
from
io
import
StringIO
def
create_view
():
app
,
admin
=
setup
()
class
MyFileAdmin
(
fileadmin
.
FileAdmin
):
editable_extensions
=
(
'txt'
,)
path
=
op
.
join
(
op
.
dirname
(
__file__
),
'files'
)
view
=
fileadmin
.
FileAdmin
(
path
,
'/files/'
,
name
=
'Files'
)
view
=
My
FileAdmin
(
path
,
'/files/'
,
name
=
'Files'
)
admin
.
add_view
(
view
)
return
app
,
admin
,
view
...
...
@@ -21,8 +30,104 @@ def test_file_admin():
client
=
app
.
test_client
()
rv
=
client
.
get
(
'/admin/fileadmin/'
)
# index
rv
=
client
.
get
(
'/admin/myfileadmin/'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
'path=dummy.txt'
in
rv
.
data
.
decode
(
'utf-8'
))
# edit
rv
=
client
.
get
(
'/admin/myfileadmin/edit/?path=dummy.txt'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
'dummy.txt'
in
rv
.
data
.
decode
(
'utf-8'
))
# TODO: Check actions, etc
rv
=
client
.
post
(
'/admin/myfileadmin/edit/?path=dummy.txt'
,
data
=
dict
(
content
=
'new_string'
))
eq_
(
rv
.
status_code
,
302
)
rv
=
client
.
get
(
'/admin/myfileadmin/edit/?path=dummy.txt'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
'dummy.txt'
in
rv
.
data
.
decode
(
'utf-8'
))
ok_
(
'new_string'
in
rv
.
data
.
decode
(
'utf-8'
))
# rename
rv
=
client
.
get
(
'/admin/myfileadmin/rename/?path=dummy.txt'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
'dummy.txt'
in
rv
.
data
.
decode
(
'utf-8'
))
rv
=
client
.
post
(
'/admin/myfileadmin/rename/?path=dummy.txt'
,
data
=
dict
(
name
=
'dummy_renamed.txt'
,
path
=
'dummy.txt'
))
eq_
(
rv
.
status_code
,
302
)
rv
=
client
.
get
(
'/admin/myfileadmin/'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
'path=dummy_renamed.txt'
in
rv
.
data
.
decode
(
'utf-8'
))
ok_
(
'path=dummy.txt'
not
in
rv
.
data
.
decode
(
'utf-8'
))
# upload
rv
=
client
.
get
(
'/admin/myfileadmin/upload/'
)
eq_
(
rv
.
status_code
,
200
)
rv
=
client
.
post
(
'/admin/myfileadmin/upload/'
,
data
=
dict
(
upload
=
(
StringIO
(
""
),
'dummy.txt'
),
))
eq_
(
rv
.
status_code
,
302
)
rv
=
client
.
get
(
'/admin/myfileadmin/'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
'path=dummy.txt'
in
rv
.
data
.
decode
(
'utf-8'
))
ok_
(
'path=dummy_renamed.txt'
in
rv
.
data
.
decode
(
'utf-8'
))
# delete
rv
=
client
.
post
(
'/admin/myfileadmin/delete/'
,
data
=
dict
(
path
=
'dummy_renamed.txt'
))
eq_
(
rv
.
status_code
,
302
)
rv
=
client
.
get
(
'/admin/myfileadmin/'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
'path=dummy_renamed.txt'
not
in
rv
.
data
.
decode
(
'utf-8'
))
ok_
(
'path=dummy.txt'
in
rv
.
data
.
decode
(
'utf-8'
))
# mkdir
rv
=
client
.
get
(
'/admin/myfileadmin/mkdir/'
)
eq_
(
rv
.
status_code
,
200
)
rv
=
client
.
post
(
'/admin/myfileadmin/mkdir/'
,
data
=
dict
(
name
=
'dummy_dir'
))
eq_
(
rv
.
status_code
,
302
)
rv
=
client
.
get
(
'/admin/myfileadmin/'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
'path=dummy.txt'
in
rv
.
data
.
decode
(
'utf-8'
))
ok_
(
'path=dummy_dir'
in
rv
.
data
.
decode
(
'utf-8'
))
# rename - directory
rv
=
client
.
get
(
'/admin/myfileadmin/rename/?path=dummy_dir'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
'dummy_dir'
in
rv
.
data
.
decode
(
'utf-8'
))
rv
=
client
.
post
(
'/admin/myfileadmin/rename/?path=dummy_dir'
,
data
=
dict
(
name
=
'dummy_renamed_dir'
,
path
=
'dummy_dir'
))
eq_
(
rv
.
status_code
,
302
)
rv
=
client
.
get
(
'/admin/myfileadmin/'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
'path=dummy_renamed_dir'
in
rv
.
data
.
decode
(
'utf-8'
))
ok_
(
'path=dummy_dir'
not
in
rv
.
data
.
decode
(
'utf-8'
))
# delete - directory
rv
=
client
.
post
(
'/admin/myfileadmin/delete/'
,
data
=
dict
(
path
=
'dummy_renamed_dir'
))
eq_
(
rv
.
status_code
,
302
)
rv
=
client
.
get
(
'/admin/myfileadmin/'
)
eq_
(
rv
.
status_code
,
200
)
ok_
(
'path=dummy_renamed_dir'
not
in
rv
.
data
.
decode
(
'utf-8'
))
ok_
(
'path=dummy.txt'
in
rv
.
data
.
decode
(
'utf-8'
))
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