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
ac5fe084
Commit
ac5fe084
authored
Mar 02, 2015
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of github.com:mrjoes/flask-admin
parents
d4480885
1b36345c
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
714 additions
and
284 deletions
+714
-284
admin.pot
babel/admin.pot
+99
-98
__init__.py
flask_admin/contrib/appengine/__init__.py
+6
-0
view.py
flask_admin/contrib/appengine/view.py
+179
-0
fileadmin.py
flask_admin/contrib/fileadmin.py
+231
-98
view.py
flask_admin/contrib/sqla/view.py
+1
-6
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
layout.html
flask_admin/templates/bootstrap2/admin/layout.html
+25
-27
inline_list_base.html
...in/templates/bootstrap2/admin/model/inline_list_base.html
+1
-1
list.html
flask_admin/templates/bootstrap3/admin/file/list.html
+4
-2
layout.html
flask_admin/templates/bootstrap3/admin/layout.html
+26
-25
inline_list_base.html
...in/templates/bootstrap3/admin/model/inline_list_base.html
+1
-1
test_fileadmin.py
flask_admin/tests/fileadmin/test_fileadmin.py
+109
-4
test_basic.py
flask_admin/tests/sqla/test_basic.py
+14
-9
No files found.
babel/admin.pot
View file @
ac5fe084
...
...
@@ -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/appengine/__init__.py
0 → 100644
View file @
ac5fe084
try
:
import
wtforms_appengine
except
ImportError
:
raise
Exception
(
'Please install wtforms_appengine in order to use appengine backend'
)
from
.view
import
ModelView
flask_admin/contrib/appengine/view.py
0 → 100644
View file @
ac5fe084
import
logging
from
flask.ext.admin.model
import
BaseModelView
from
wtforms_appengine
import
db
as
wt_db
from
wtforms_appengine
import
ndb
as
wt_ndb
from
google.appengine.ext
import
db
from
google.appengine.ext
import
ndb
class
NdbModelView
(
BaseModelView
):
"""
AppEngine NDB model scaffolding.
"""
def
get_pk_value
(
self
,
model
):
return
model
.
key
.
urlsafe
()
def
scaffold_list_columns
(
self
):
return
sorted
([
k
for
(
k
,
v
)
in
self
.
model
.
__dict__
.
iteritems
()
if
isinstance
(
v
,
ndb
.
Property
)])
def
scaffold_sortable_columns
(
self
):
return
[
k
for
(
k
,
v
)
in
self
.
model
.
__dict__
.
iteritems
()
if
isinstance
(
v
,
ndb
.
Property
)
and
v
.
_indexed
]
def
init_search
(
self
):
return
None
def
is_valid_filter
(
self
):
pass
def
scaffold_filters
(
self
):
#TODO: implement
pass
def
scaffold_form
(
self
):
return
wt_ndb
.
model_form
(
self
.
model
())
def
get_list
(
self
,
page
,
sort_field
,
sort_desc
,
search
,
filters
):
#TODO: implement filters (don't think search can work here)
q
=
self
.
model
.
query
()
if
sort_field
:
order_field
=
getattr
(
self
.
model
,
sort_field
)
if
sort_desc
:
order_field
=
-
order_field
q
=
q
.
order
(
order_field
)
results
=
q
.
fetch
(
self
.
page_size
,
offset
=
page
*
self
.
page_size
)
return
q
.
count
(),
results
def
get_one
(
self
,
urlsafe_key
):
return
ndb
.
Key
(
urlsafe
=
urlsafe_key
)
.
get
()
def
create_model
(
self
,
form
):
try
:
model
=
self
.
model
()
form
.
populate_obj
(
model
)
model
.
put
()
return
True
except
Exception
as
ex
:
if
not
self
.
handle_view_exception
(
ex
):
#flash(gettext('Failed to create record. %(error)s',
# error=ex), 'error')
logging
.
exception
(
'Failed to create record.'
)
return
False
def
update_model
(
self
,
form
,
model
):
try
:
form
.
populate_obj
(
model
)
model
.
put
()
return
True
except
Exception
as
ex
:
if
not
self
.
handle_view_exception
(
ex
):
#flash(gettext('Failed to update record. %(error)s',
# error=ex), 'error')
logging
.
exception
(
'Failed to update record.'
)
return
False
def
delete_model
(
self
,
model
):
try
:
model
.
key
.
delete
()
return
True
except
Exception
as
ex
:
if
not
self
.
handle_view_exception
(
ex
):
#flash(gettext('Failed to delete record. %(error)s',
# error=ex),
# 'error')
logging
.
exception
(
'Failed to delete record.'
)
return
False
class
DbModelView
(
BaseModelView
):
"""
AppEngine DB model scaffolding.
"""
def
get_pk_value
(
self
,
model
):
return
str
(
model
.
key
())
def
scaffold_list_columns
(
self
):
return
sorted
([
k
for
(
k
,
v
)
in
self
.
model
.
__dict__
.
iteritems
()
if
isinstance
(
v
,
db
.
Property
)])
def
scaffold_sortable_columns
(
self
):
return
[
k
for
(
k
,
v
)
in
self
.
model
.
__dict__
.
iteritems
()
if
isinstance
(
v
,
db
.
Property
)
and
v
.
_indexed
]
def
init_search
(
self
):
return
None
def
is_valid_filter
(
self
):
pass
def
scaffold_filters
(
self
):
#TODO: implement
pass
def
scaffold_form
(
self
):
return
wt_db
.
model_form
(
self
.
model
())
def
get_list
(
self
,
page
,
sort_field
,
sort_desc
,
search
,
filters
):
#TODO: implement filters (don't think search can work here)
q
=
self
.
model
.
all
()
if
sort_field
:
if
sort_desc
:
sort_field
=
"-"
+
sort_field
q
.
order
(
sort_field
)
results
=
q
.
fetch
(
self
.
page_size
,
offset
=
page
*
self
.
page_size
)
return
q
.
count
(),
results
def
get_one
(
self
,
encoded_key
):
return
db
.
get
(
db
.
Key
(
encoded
=
encoded_key
))
def
create_model
(
self
,
form
):
try
:
model
=
self
.
model
()
form
.
populate_obj
(
model
)
model
.
put
()
return
True
except
Exception
as
ex
:
if
not
self
.
handle_view_exception
(
ex
):
#flash(gettext('Failed to create record. %(error)s',
# error=ex), 'error')
logging
.
exception
(
'Failed to create record.'
)
return
False
def
update_model
(
self
,
form
,
model
):
try
:
form
.
populate_obj
(
model
)
model
.
put
()
return
True
except
Exception
as
ex
:
if
not
self
.
handle_view_exception
(
ex
):
#flash(gettext('Failed to update record. %(error)s',
# error=ex), 'error')
logging
.
exception
(
'Failed to update record.'
)
return
False
def
delete_model
(
self
,
model
):
try
:
model
.
delete
()
return
True
except
Exception
as
ex
:
if
not
self
.
handle_view_exception
(
ex
):
#flash(gettext('Failed to delete record. %(error)s',
# error=ex),
# 'error')
logging
.
exception
(
'Failed to delete record.'
)
return
False
def
ModelView
(
model
):
if
issubclass
(
model
,
ndb
.
Model
):
return
NdbModelView
(
model
)
elif
issubclass
(
model
,
db
.
Model
):
return
DbModelView
(
model
)
else
:
raise
ValueError
(
"Unsupported model:
%
s"
%
model
)
flask_admin/contrib/fileadmin.py
View file @
ac5fe084
...
...
@@ -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/contrib/sqla/view.py
View file @
ac5fe084
...
...
@@ -80,8 +80,7 @@ class ModelView(BaseModelView):
'searchable_columns'
,
None
)
"""
Collection of the searchable columns. Only text-based columns
are searchable (`String`, `Unicode`, `Text`, `UnicodeText`).
Collection of the searchable columns.
Example::
...
...
@@ -491,10 +490,6 @@ class ModelView(BaseModelView):
for
column
in
self
.
_get_columns_for_field
(
attr
):
column_type
=
type
(
column
.
type
)
.
__name__
if
not
self
.
is_text_column_type
(
column_type
):
raise
Exception
(
'Can only search on text columns. '
+
'Failed to setup search for "
%
s"'
%
p
)
self
.
_search_fields
.
append
(
column
)
# Store joins, avoid duplicates
...
...
flask_admin/helpers.py
View file @
ac5fe084
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 @
ac5fe084
...
...
@@ -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 @
ac5fe084
...
...
@@ -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/bootstrap2/admin/layout.html
View file @
ac5fe084
{% macro menu_icon(item) -%}
{% set icon_type = item.get_icon_type() %}
{% if icon_type %}
{%
-
if icon_type %}
{% set icon_value = item.get_icon_value() %}
{% if icon_type == 'glyph' %}
<i
class=
"{{ icon_value }}"
></i>
...
...
@@ -13,45 +13,43 @@
{%- endmacro %}
{% macro menu() %}
{% for item in admin_view.admin.menu() %}
{%
if item.is_category()
%}
{%
-
for item in admin_view.admin.menu() %}
{%
- if item.is_category() -
%}
{% set children = item.get_children() %}
{% if children %}
{%
-
if children %}
{% set class_name = item.get_class_name() %}
{% if item.is_active(admin_view) %}
{%
-
if item.is_active(admin_view) %}
<li
class=
"active dropdown{% if class_name %} {{class_name}}{% endif %}"
>
{% else %}
{% else
-
%}
<li
class=
"dropdown{% if class_name %} {{class_name}}{% endif %}"
>
{% endif %}
<a
class=
"dropdown-toggle"
data-toggle=
"dropdown"
href=
"javascript:void(0)"
>
{{ menu_icon(item) }}{{ item.name }}
<b
class=
"caret"
></b>
</a>
{%- endif %}
<a
class=
"dropdown-toggle"
data-toggle=
"dropdown"
href=
"javascript:void(0)"
>
{{ menu_icon(item) }}{{ item.name }}
<b
class=
"caret"
></b></a>
<ul
class=
"dropdown-menu"
>
{% for child in children
%}
{% set class_name = child.get_class_name() %}
{%
if child.is_active(admin_view) %}
<li
class=
"active{% if class_name %} {{class_name}}{% endif %}"
>
{% else %}
<li
{%
if
class_name
%}
class=
"{{class_name}}"
{%
endif
%}
>
{%
endif %}
<a
href=
"{{ child.get_url() }}"
>
{{ menu_icon(child) }}{{ child.name }}
</a>
</li>
{%
endfor %}
{%- for child in children -
%}
{% set class_name = child.get_class_name() %}
{%-
if child.is_active(admin_view) %}
<li
class=
"active{% if class_name %} {{class_name}}{% endif %}"
>
{% else %}
<li
{%
if
class_name
%}
class=
"{{class_name}}"
{%
endif
%}
>
{%-
endif %}
<a
href=
"{{ child.get_url() }}"
>
{{ menu_icon(child) }}{{ child.name }}
</a>
</li>
{%-
endfor %}
</ul>
</li>
{% endif %}
{% else %}
{%
if item.is_accessible() and item.is_visible()
%}
{%
-
else %}
{%
- if item.is_accessible() and item.is_visible() -
%}
{% set class_name = item.get_class_name() %}
{% if item.is_active(admin_view) %}
{%
-
if item.is_active(admin_view) %}
<li
class=
"active{% if class_name %} {{class_name}}{% endif %}"
>
{% else %}
{%
-
else %}
<li
{%
if
class_name
%}
class=
"{{class_name}}"
{%
endif
%}
>
{% endif %}
{%
-
endif %}
<a
href=
"{{ item.get_url() }}"
>
{{ menu_icon(item) }}{{ item.name }}
</a>
</li>
{%
endif
%}
{% endif %}
{%
- endif -
%}
{% endif
-
%}
{% endfor %}
{% endmacro %}
...
...
flask_admin/templates/bootstrap2/admin/model/inline_list_base.html
View file @
ac5fe084
{% macro render_inline_fields(field, template, render, check=None) %}
<div
class=
"inline-field"
>
<div
class=
"inline-field"
id=
"{{ field.id }}"
>
{# existing inline form fields #}
<div
class=
"inline-field-list"
>
{% for subfield in field %}
...
...
flask_admin/templates/bootstrap3/admin/file/list.html
View file @
ac5fe084
...
...
@@ -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/templates/bootstrap3/admin/layout.html
View file @
ac5fe084
{% macro menu_icon(item) -%}
{% set icon_type = item.get_icon_type() %}
{% if icon_type %}
{%
-
if icon_type %}
{% set icon_value = item.get_icon_value() %}
{% if icon_type == 'glyph' %}
<i
class=
"glyphicon {{ icon_value }}"
></i>
...
...
@@ -13,42 +13,43 @@
{%- endmacro %}
{% macro menu() %}
{% for item in admin_view.admin.menu() %}
{%
if item.is_category()
%}
{%
-
for item in admin_view.admin.menu() %}
{%
- if item.is_category() -
%}
{% set children = item.get_children() %}
{% if children %}
{% if item.is_active(admin_view) %}
{%- if children %}
{% set class_name = item.get_class_name() %}
{%- if item.is_active(admin_view) %}
<li
class=
"active dropdown{% if class_name %} {{class_name}}{% endif %}"
>
{% else %}
{% else
-
%}
<li
class=
"dropdown{% if class_name %} {{class_name}}{% endif %}"
>
{% endif %}
{%
-
endif %}
<a
class=
"dropdown-toggle"
data-toggle=
"dropdown"
href=
"javascript:void(0)"
>
{{ menu_icon(item) }}{{ item.name }}
<b
class=
"caret"
></b></a>
<ul
class=
"dropdown-menu"
>
{% for child in children
%}
{% set class_name = child.get_class_name() %}
{%
if child.is_active(admin_view) %}
<li
class=
"active"
{%
if
class_name
%}
{{
class_name
}}{%
endif
%}
>
{% else %}
<li
{%
if
class_name
%}
class=
"{{class_name}}"
{%
endif
%}
>
{%
endif %}
<a
href=
"{{ child.get_url() }}"
>
{{ menu_icon(child) }}{{ child.name }}
</a>
</li>
{%
endfor %}
{%- for child in children -
%}
{% set class_name = child.get_class_name() %}
{%-
if child.is_active(admin_view) %}
<li
class=
"active{% if class_name %} {{class_name}}{% endif %}"
>
{% else %}
<li
{%
if
class_name
%}
class=
"{{class_name}}"
{%
endif
%}
>
{%-
endif %}
<a
href=
"{{ child.get_url() }}"
>
{{ menu_icon(child) }}{{ child.name }}
</a>
</li>
{%-
endfor %}
</ul>
</li>
{% endif %}
{% else %}
{%
if item.is_accessible() and item.is_visible()
%}
{%
-
else %}
{%
- if item.is_accessible() and item.is_visible() -
%}
{% set class_name = item.get_class_name() %}
{% if item.is_active(admin_view) %}
<li
class=
"active
"
{%
if
class_name
%}
{{
class_name
}}{%
endif
%}
>
{% else %}
{%
-
if item.is_active(admin_view) %}
<li
class=
"active
{% if class_name %} {{class_name}}{% endif %}"
>
{%
-
else %}
<li
{%
if
class_name
%}
class=
"{{class_name}}"
{%
endif
%}
>
{% endif %}
{%
-
endif %}
<a
href=
"{{ item.get_url() }}"
>
{{ menu_icon(item) }}{{ item.name }}
</a>
</li>
{%
endif
%}
{% endif %}
{%
- endif -
%}
{% endif
-
%}
{% endfor %}
{% endmacro %}
...
...
flask_admin/templates/bootstrap3/admin/model/inline_list_base.html
View file @
ac5fe084
{% macro render_inline_fields(field, template, render, check=None) %}
<div
class=
"inline-field"
>
<div
class=
"inline-field"
id=
"{{ field.id }}"
>
{# existing inline form fields #}
<div
class=
"inline-field-list"
>
{% for subfield in field %}
...
...
flask_admin/tests/fileadmin/test_fileadmin.py
View file @
ac5fe084
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'
))
flask_admin/tests/sqla/test_basic.py
View file @
ac5fe084
...
...
@@ -253,27 +253,32 @@ def test_column_searchable_list():
Model1
,
Model2
=
create_models
(
db
)
view
=
CustomModelView
(
Model
1
,
db
.
session
,
column_searchable_list
=
[
'
test1'
,
'test2
'
])
view
=
CustomModelView
(
Model
2
,
db
.
session
,
column_searchable_list
=
[
'
string_field'
,
'int_field
'
])
admin
.
add_view
(
view
)
eq_
(
view
.
_search_supported
,
True
)
eq_
(
len
(
view
.
_search_fields
),
2
)
ok_
(
isinstance
(
view
.
_search_fields
[
0
],
db
.
Column
))
ok_
(
isinstance
(
view
.
_search_fields
[
1
],
db
.
Column
))
eq_
(
view
.
_search_fields
[
0
]
.
name
,
'
test1
'
)
eq_
(
view
.
_search_fields
[
1
]
.
name
,
'
test2
'
)
eq_
(
view
.
_search_fields
[
0
]
.
name
,
'
string_field
'
)
eq_
(
view
.
_search_fields
[
1
]
.
name
,
'
int_field
'
)
db
.
session
.
add
(
Model
1
(
'model1'
))
db
.
session
.
add
(
Model
1
(
'model2'
))
db
.
session
.
add
(
Model
2
(
'model1-test'
,
5000
))
db
.
session
.
add
(
Model
2
(
'model2-test'
,
9000
))
db
.
session
.
commit
()
client
=
app
.
test_client
()
rv
=
client
.
get
(
'/admin/model1/?search=model1'
)
rv
=
client
.
get
(
'/admin/model2/?search=model1'
)
data
=
rv
.
data
.
decode
(
'utf-8'
)
ok_
(
'model1-test'
in
data
)
ok_
(
'model2-test'
not
in
data
)
rv
=
client
.
get
(
'/admin/model2/?search=9000'
)
data
=
rv
.
data
.
decode
(
'utf-8'
)
ok_
(
'model1
'
in
data
)
ok_
(
'model2
'
not
in
data
)
ok_
(
'model1
-test'
not
in
data
)
ok_
(
'model2
-test'
in
data
)
def
test_complex_searchable_list
():
...
...
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