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
f89f33fd
Commit
f89f33fd
authored
Mar 22, 2012
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
File admin, use POST to delete models/files.
parent
18b3c956
Changes
13
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
612 additions
and
41 deletions
+612
-41
TODO.txt
TODO.txt
+4
-3
file.py
examples/file/file.py
+38
-0
simple.py
examples/simple/simple.py
+0
-1
fileadmin.py
flask_adminex/ext/fileadmin.py
+412
-0
sqlamodel.py
flask_adminex/ext/sqlamodel.py
+0
-2
model.py
flask_adminex/model.py
+2
-2
admin.css
flask_adminex/static/css/admin.css
+16
-0
form.html
flask_adminex/templates/admin/file/form.html
+6
-0
list.html
flask_adminex/templates/admin/file/list.html
+80
-0
rename.html
flask_adminex/templates/admin/file/rename.html
+7
-0
lib.html
flask_adminex/templates/admin/lib.html
+39
-0
edit.html
flask_adminex/templates/admin/model/edit.html
+2
-29
list.html
flask_adminex/templates/admin/model/list.html
+6
-4
No files found.
TODO.txt
View file @
f89f33fd
- Core
- Right-side menu items (auth?)
- Pregenerate URLs for menu
- Conditional js include for forms or pages
- Flask app in constructor
- Calendar - add validation for time without seconds (automatically add seconds)
- Model Admin
- Ability to sort by fields that are not visible?
- SQLA Model Admin
...
...
@@ -12,5 +12,6 @@
- Many2Many support
- WYSIWYG editor support
- File admin
- Documentation
- Header title
- Mass-delete functionality
- Unit tests
examples/file/file.py
0 → 100644
View file @
f89f33fd
import
os
import
os.path
as
op
from
flask
import
Flask
from
flask.ext
import
adminex
from
flask.ext.adminex.ext
import
fileadmin
# Create flask app
app
=
Flask
(
__name__
,
template_folder
=
'templates'
,
static_folder
=
'files'
)
# Create dummy secrey key so we can use flash
app
.
config
[
'SECRET_KEY'
]
=
'123456790'
# Flask views
@
app
.
route
(
'/'
)
def
index
():
return
'<a href="/admin/">Click me to get to Admin!</a>'
if
__name__
==
'__main__'
:
# Create directory
path
=
op
.
join
(
op
.
dirname
(
__file__
),
'files'
)
try
:
os
.
mkdir
(
path
)
except
OSError
:
pass
# Create admin interface
admin
=
adminex
.
Admin
()
admin
.
add_view
(
fileadmin
.
FileAdmin
(
path
,
'/files/'
,
name
=
'Files'
))
admin
.
setup_app
(
app
)
# Start app
app
.
debug
=
True
app
.
run
()
examples/simple/simple.py
View file @
f89f33fd
...
...
@@ -23,7 +23,6 @@ class AnotherAdminView(adminex.BaseView):
# Create flask app
app
=
Flask
(
__name__
,
template_folder
=
'templates'
)
# Flask views
@
app
.
route
(
'/'
)
def
index
():
...
...
flask_adminex/ext/fileadmin.py
0 → 100644
View file @
f89f33fd
import
os
import
os.path
as
op
import
platform
import
urlparse
import
re
import
shutil
from
operator
import
itemgetter
from
flask
import
flash
,
url_for
,
redirect
,
abort
,
request
from
werkzeug
import
secure_filename
from
flask.ext.adminex.base
import
BaseView
,
expose
from
flask.ext.adminex
import
form
from
flask.ext
import
wtf
class
NameForm
(
wtf
.
Form
):
"""
Form with a filename input field.
Validates if provided name is valid for *nix and Windows systems.
"""
name
=
wtf
.
TextField
()
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
wtf
.
ValidationError
(
'Invalid directory name'
)
class
UploadForm
(
form
.
AdminForm
):
"""
File upload form. Works with FileAdmin instance to check if it is allowed
to upload file with given extension.
"""
upload
=
wtf
.
FileField
(
'File to upload'
,
validators
=
[
wtf
.
file_required
()])
def
__init__
(
self
,
form
,
admin
):
self
.
admin
=
admin
super
(
UploadForm
,
self
)
.
__init__
(
form
)
def
validate_upload
(
self
,
field
):
filename
=
field
.
file
.
filename
if
not
self
.
admin
.
is_file_allowed
(
filename
):
raise
wtf
.
ValidationError
(
'Invalid file type'
)
class
FileAdmin
(
BaseView
):
"""
Simple file-management interface.
Requires two parameters:
`path`
Path to the directory which will be managed
`url`
Base URL for the directory. Will be used to generate
static links to the files.
Sample usage::
admin = Admin()
path = op.join(op.dirname(__file__), 'static')
admin.add_view(path, '/static/', name='Static Files')
admin.setup_app(app)
"""
can_upload
=
True
"""
Is file upload allowed.
"""
can_delete
=
True
"""
Is file deletion allowed.
"""
can_delete_dirs
=
True
"""
Is recursive directory deletion is allowed.
"""
can_mkdir
=
True
"""
Is directory creation allowed.
"""
can_rename
=
True
"""
Is file and directory renaming allowed.
"""
allowed_extensions
=
None
"""
List of allowed extensions for uploads, in lower case.
Example::
class MyAdmin(FileAdmin):
allowed_extensions = ('swf', 'jpg', 'gif', 'png')
"""
def
__init__
(
self
,
base_path
,
base_url
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
):
"""
Constructor.
`base_path`
Base file storage location
`base_url`
Base URL for the files
`name`
Name of this view. If not provided, will be defaulted to the class name.
`category`
View category
`endpoint`
Endpoint name for the view
`url`
URL for view
"""
self
.
base_path
=
base_path
self
.
base_url
=
base_url
self
.
_on_windows
=
platform
.
system
()
==
'Windows'
# Convert allowed_extensions to set for quick validation
if
(
self
.
allowed_extensions
and
not
isinstance
(
self
.
allowed_extensions
,
set
)):
self
.
allowed_extensions
=
set
(
self
.
allowed_extensions
)
super
(
FileAdmin
,
self
)
.
__init__
(
name
,
category
,
endpoint
,
url
)
def
is_accessible_path
(
self
,
path
):
"""
Verify if path is accessible for current user.
Override to customize behavior.
`path`
Relative path to the root
"""
return
True
def
get_base_path
(
self
):
"""
Return base path. Override to customize behavior (per-user
directories, etc)
"""
return
self
.
base_path
def
get_base_url
(
self
):
"""
Return base URL. Override to customize behavior (per-user
directories, etc)
"""
return
self
.
base_url
def
is_file_allowed
(
self
,
filename
):
"""
Verify if file can be uploaded.
Override to customize behavior.
`filename`
Source file name
"""
ext
=
op
.
splitext
(
filename
)[
1
]
.
lower
()
if
ext
.
startswith
(
'.'
):
ext
=
ext
[
1
:]
if
self
.
allowed_extensions
and
ext
not
in
self
.
allowed_extensions
:
return
False
return
True
def
is_in_folder
(
self
,
base_path
,
directory
):
"""
Verify if `directory` is in `base_path` folder
"""
return
op
.
normpath
(
directory
)
.
startswith
(
base_path
)
def
_get_dir_url
(
self
,
endpoint
,
path
,
**
kwargs
):
"""
Return prettified URL
`endpoint`
Endpoint name
`path`
Directory path
`kwargs`
Additional arguments
"""
if
not
path
:
return
url_for
(
endpoint
)
else
:
if
self
.
_on_windows
:
path
=
path
.
replace
(
'
\\
'
,
'/'
)
kwargs
[
'path'
]
=
path
return
url_for
(
endpoint
,
**
kwargs
)
def
_get_file_url
(
self
,
path
):
"""
Return static file url
`path`
Static file path
"""
base_url
=
self
.
get_base_url
()
return
urlparse
.
urljoin
(
base_url
,
path
)
def
_normalize_path
(
self
,
path
):
"""
Verify and normalize path.
If path is not relative to the base directory, will throw 404 exception.
If path does not exist, will also throw 404 exception.
"""
base_path
=
self
.
get_base_path
()
if
path
is
None
:
directory
=
base_path
path
=
''
else
:
path
=
op
.
normpath
(
path
)
directory
=
op
.
normpath
(
op
.
join
(
base_path
,
path
))
if
not
self
.
is_in_folder
(
base_path
,
directory
):
abort
(
404
)
if
not
op
.
exists
(
directory
):
abort
(
404
)
return
base_path
,
directory
,
path
@
expose
(
'/'
)
@
expose
(
'/b/<path:path>'
)
def
index
(
self
,
path
=
None
):
"""
Index view method
`path`
Optional directory path. If not provided, will use base directory
"""
# Get path and verify if it is valid
base_path
,
directory
,
path
=
self
.
_normalize_path
(
path
)
# Get directory listing
items
=
[]
# Parent directory
if
directory
!=
base_path
:
parent_path
=
op
.
normpath
(
op
.
join
(
path
,
'..'
))
if
parent_path
==
'.'
:
parent_path
=
None
items
.
append
((
'..'
,
parent_path
,
True
,
0
))
for
f
in
os
.
listdir
(
directory
):
fp
=
op
.
join
(
directory
,
f
)
items
.
append
((
f
,
op
.
join
(
path
,
f
),
op
.
isdir
(
fp
),
op
.
getsize
(
fp
)))
# Sort by type
items
.
sort
(
key
=
itemgetter
(
2
),
reverse
=
True
)
# Generate breadcrumbs
accumulator
=
''
breadcrumbs
=
[(
n
,
op
.
join
(
accumulator
,
n
))
for
n
in
path
.
split
(
os
.
sep
)]
print
breadcrumbs
,
path
,
breadcrumbs
[:
-
1
],
breadcrumbs
[
-
1
]
return
self
.
render
(
'admin/file/list.html'
,
dir_path
=
path
,
breadcrumbs
=
breadcrumbs
,
get_dir_url
=
self
.
_get_dir_url
,
get_file_url
=
self
.
_get_file_url
,
items
=
items
)
@
expose
(
'/upload/'
,
methods
=
(
'GET'
,
'POST'
))
@
expose
(
'/upload/<path:path>'
,
methods
=
(
'GET'
,
'POST'
))
def
upload
(
self
,
path
=
None
):
"""
Upload view method
`path`
Optional directory path. If not provided, will use base directory
"""
# Get path and verify if it is valid
base_path
,
directory
,
path
=
self
.
_normalize_path
(
path
)
form
=
UploadForm
(
request
.
form
,
self
)
if
form
.
validate_on_submit
():
filename
=
op
.
join
(
directory
,
secure_filename
(
form
.
upload
.
file
.
filename
))
if
op
.
exists
(
filename
):
flash
(
'File "
%
s" already exists.'
%
form
.
upload
.
file
.
filename
,
'error'
)
else
:
form
.
upload
.
file
.
save
(
filename
)
return
redirect
(
self
.
_get_dir_url
(
'.index'
,
path
))
return
self
.
render
(
'admin/file/form.html'
,
form
=
form
)
@
expose
(
'/mkdir/'
,
methods
=
(
'GET'
,
'POST'
))
@
expose
(
'/mkdir/<path:path>'
,
methods
=
(
'GET'
,
'POST'
))
def
mkdir
(
self
,
path
=
None
):
"""
Directory creation view method
`path`
Optional directory path. If not provided, will use base directory
"""
# Get path and verify if it is valid
base_path
,
directory
,
path
=
self
.
_normalize_path
(
path
)
dir_url
=
self
.
_get_dir_url
(
'.index'
,
path
)
form
=
NameForm
(
request
.
form
)
if
form
.
validate_on_submit
():
try
:
os
.
mkdir
(
op
.
join
(
directory
,
form
.
name
.
data
))
return
redirect
(
dir_url
)
except
Exception
,
ex
:
flash
(
'Failed to create directory:
%
s'
%
ex
,
'error'
)
return
self
.
render
(
'admin/file/form.html'
,
form
=
form
,
dir_url
=
dir_url
)
@
expose
(
'/delete/'
,
methods
=
(
'POST'
,))
def
delete
(
self
):
"""
Delete view method
"""
path
=
request
.
form
.
get
(
'path'
)
if
not
path
:
return
redirect
(
url_for
(
'.index'
))
# Get path and verify if it is valid
base_path
,
full_path
,
path
=
self
.
_normalize_path
(
path
)
return_url
=
self
.
_get_dir_url
(
'.index'
,
op
.
dirname
(
path
))
if
op
.
isdir
(
full_path
):
if
not
self
.
can_delete_dirs
:
return
redirect
(
return_url
)
try
:
shutil
.
rmtree
(
full_path
)
flash
(
'Directory "
%
s" was successfully deleted.'
%
path
)
except
Exception
,
ex
:
flash
(
'Failed to delete directory:
%
s'
%
ex
,
'error'
)
else
:
try
:
os
.
remove
(
full_path
)
flash
(
'File "
%
s" was successfully deleted.'
%
path
)
except
Exception
,
ex
:
flash
(
'Failed to delete file:
%
s'
%
ex
,
'error'
)
return
redirect
(
return_url
)
@
expose
(
'/rename/'
,
methods
=
(
'GET'
,
'POST'
))
def
rename
(
self
):
"""
Rename view method
"""
path
=
request
.
args
.
get
(
'path'
)
if
not
path
:
return
redirect
(
url_for
(
'.index'
))
base_path
,
full_path
,
path
=
self
.
_normalize_path
(
path
)
return_url
=
self
.
_get_dir_url
(
'.index'
,
op
.
dirname
(
path
))
if
not
op
.
exists
(
full_path
):
flash
(
'Path does not exist.'
)
return
redirect
(
return_url
)
form
=
NameForm
(
request
.
form
,
name
=
op
.
basename
(
path
))
if
form
.
validate_on_submit
():
try
:
dir_base
=
op
.
dirname
(
full_path
)
filename
=
secure_filename
(
form
.
name
.
data
)
os
.
rename
(
full_path
,
op
.
join
(
dir_base
,
filename
))
flash
(
'Successfully renamed "
%
s" to "
%
s"'
%
(
op
.
basename
(
path
),
filename
))
except
Exception
,
ex
:
flash
(
'Failed to rename:
%
s'
%
ex
,
'error'
)
return
redirect
(
return_url
)
return
self
.
render
(
'admin/file/rename.html'
,
form
=
form
,
path
=
op
.
dirname
(
path
),
name
=
op
.
basename
(
path
),
dir_url
=
return_url
)
flask_adminex/ext/sqlamodel.py
View file @
f89f33fd
...
...
@@ -34,8 +34,6 @@ class AdminModelConverter(ModelConverter):
'default'
:
None
}
print
prop
,
kwargs
,
local_column
if
field_args
:
kwargs
.
update
(
field_args
)
...
...
flask_adminex/model.py
View file @
f89f33fd
...
...
@@ -494,10 +494,10 @@ class BaseModelView(BaseView):
form
=
form
,
return_url
=
return_url
or
url_for
(
'.index_view'
))
@
expose
(
'/delete/<int:id>/'
)
@
expose
(
'/delete/<int:id>/'
,
methods
=
(
'POST'
,)
)
def
delete_view
(
self
,
id
):
"""
Delete model view
Delete model view
. Only POST method is allowed.
"""
return_url
=
request
.
args
.
get
(
'return'
)
...
...
flask_adminex/static/css/admin.css
View file @
f89f33fd
...
...
@@ -3,3 +3,19 @@ body
{
padding-top
:
50px
;
}
form
.icon
{
display
:
inline
;
}
form
.icon
button
{
border
:
none
;
background
:
transparent
;
text-decoration
:
none
;
padding
:
0
;
line-height
:
normal
;
}
a
.icon
{
text-decoration
:
none
;
}
flask_adminex/templates/admin/file/form.html
0 → 100644
View file @
f89f33fd
{% extends 'admin/master.html' %}
{% import 'admin/lib.html' as lib %}
{% block body %}
{{ lib.render_form(form, dir_url) }}
{% endblock %}
\ No newline at end of file
flask_adminex/templates/admin/file/list.html
0 → 100644
View file @
f89f33fd
{% extends 'admin/master.html' %}
{% import 'admin/lib.html' as lib %}
{% block body %}
<ul
class=
"breadcrumb"
>
<li>
<a
href=
"{{ get_dir_url('.index', path=None) }}"
>
Root
</a>
</li>
{% for name, path in breadcrumbs[:-1] %}
<li>
<span
class=
"divider"
>
/
</span><a
href=
"{{ get_dir_url('.index', path=path) }}"
>
{{ name }}
</a>
</li>
{% endfor %}
{% if breadcrumbs %}
<li>
<span
class=
"divider"
>
/
</span><a
href=
"{{ get_dir_url('.index', path=breadcrumbs[-1][1]) }}"
>
{{ breadcrumbs[-1][0] }}
</a>
</li>
{% endif %}
</ul>
<table
class=
"table table-striped table-bordered model-list"
>
<thead>
<tr>
<th
class=
"span1"
>
</th>
<th>
Name
</th>
<th>
Size
</th>
</tr>
</thead>
{% for name, path, is_dir, size in items %}
<tr>
<td>
{% if admin_view.can_rename and path and name != '..' %}
<a
class=
"icon"
href=
"{{ url_for('.rename', path=path) }}"
>
<i
class=
"icon-pencil"
></i>
</a>
{% endif %}
{%- if admin_view.can_delete and path -%}
{% if is_dir %}
{% if name != '..' %}
<form
class=
"icon"
method=
"POST"
action=
"{{ url_for('.delete') }}"
>
<input
type=
"hidden"
name=
"path"
value=
"{{ path }}"
></input>
<button
onclick=
"return confirm('Are you sure you want to delete \'{{ name }}\' recursively?')"
>
<i
class=
"icon-remove"
></i>
</button>
</form>
{% endif %}
{% else %}
<form
class=
"icon"
method=
"POST"
action=
"{{ url_for('.delete') }}"
>
<input
type=
"hidden"
name=
"path"
value=
"{{ path }}"
></input>
<button
onclick=
"return confirm('Are you sure you want to delete \'{{ name }}\'?')"
>
<i
class=
"icon-remove"
></i>
</button>
</form>
{% endif %}
{%- endif -%}
</td>
{% if is_dir %}
<td
colspan=
"2"
>
<a
href=
"{{ get_dir_url('.index', path)|safe }}"
>
<i
class=
"icon-folder-close"
></i>
<span>
{{ name }}
</span>
</a>
</td>
{% else %}
<td>
<a
href=
"{{ get_file_url(path)|safe }}"
>
{{ name }}
</a>
</td>
<td>
{{ size }}
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% if admin_view.can_upload %}
<a
class=
"btn btn-primary btn-large"
href=
"{{ get_dir_url('.upload', path=dir_path) }}"
>
Upload File
</a>
{% endif %}
{% if admin_view.can_mkdir %}
<a
class=
"btn btn-primary btn-large"
href=
"{{ get_dir_url('.mkdir', path=dir_path) }}"
>
Create Directory
</a>
{% endif %}
{% endblock %}
flask_adminex/templates/admin/file/rename.html
0 → 100644
View file @
f89f33fd
{% extends 'admin/master.html' %}
{% import 'admin/lib.html' as lib %}
{% block body %}
<h3>
Please provide new name for
<i>
{{ name }}
</i></h3>
{{ lib.render_form(form, dir_url) }}
{% endblock %}
\ No newline at end of file
flask_adminex/templates/admin/lib.html
View file @
f89f33fd
...
...
@@ -72,3 +72,42 @@
</div>
{% endif %}
{%- endmacro %}
{% macro render_form(form, cancel_url) -%}
<form
action=
""
method=
"POST"
class=
"form-horizontal"
{%
if
form
.
has_file_field
%}
enctype=
"multipart/form-data"
{%
endif
%}
>
<fieldset>
{{ form.csrf }}
{% for f in form if f.label.text != 'Csrf' %}
<div
class=
"control-group{% if f.errors %} error{% endif %}"
>
{{ f.label(class='control-label') }}
<div
class=
"controls"
>
<div>
{% if not focus_set %}
{{ f(autofocus='autofocus') }}
{% set focus_set = True %}
{% else %}
{{ f() }}
{% endif %}
</div>
{% if f.errors %}
<ul>
{% for e in f.errors %}
<li>
{{ e }}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% endfor %}
<div
class=
"control-group"
>
<div
class=
"controls"
>
<input
type=
"submit"
class=
"btn btn-primary btn-large"
/>
{% if cancel_url %}
<a
href=
"{{ cancel_url }}"
class=
"btn btn-large"
>
Cancel
</a>
{% endif %}
</div>
</div>
</fieldset>
</form>
{% endmacro %}
flask_adminex/templates/admin/model/edit.html
View file @
f89f33fd
{% extends 'admin/master.html' %}
{% import 'admin/lib.html' as lib %}
{% block head %}
<link
href=
"{{ url_for('admin.static', filename='chosen/chosen.css') }}"
rel=
"stylesheet"
>
...
...
@@ -6,35 +7,7 @@
{% endblock %}
{% block body %}
<form
action=
""
method=
"POST"
class=
"form-horizontal"
{%
if
form
.
has_file_field
%}
enctype=
"multipart/form-data"
{%
endif
%}
>
<fieldset>
{{ form.csrf }}
{% for f in form if f.label.text != 'Csrf' %}
<div
class=
"control-group{% if f.errors %} error{% endif %}"
>
{{ f.label(class='control-label') }}
<div
class=
"controls"
>
<div>
{{ f }}
</div>
{% if f.errors %}
<ul>
{% for e in f.errors %}
<li>
{{ e }}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% endfor %}
<div
class=
"control-group"
>
<div
class=
"controls"
>
<input
type=
"submit"
class=
"btn btn-primary btn-large"
/>
<a
href=
"{{ return_url }}"
class=
"btn btn-large"
>
Cancel
</a>
</div>
</div>
</fieldset>
</form>
{{ lib.render_form(form, return_url) }}
{% endblock %}
{% block tail %}
...
...
flask_adminex/templates/admin/model/list.html
View file @
f89f33fd
...
...
@@ -34,14 +34,16 @@
<tr>
<td>
{%- if admin_view.can_edit -%}
<a
href=
"{{ url_for('.edit_view', id=row.id, return=return_url) }}"
>
<a
class=
"icon"
href=
"{{ url_for('.edit_view', id=row.id, return=return_url) }}"
>
<i
class=
"icon-pencil"
></i>
</a>
{%- endif -%}
{%- if admin_view.can_delete -%}
<a
href=
"{{ url_for('.delete_view', id=row.id, return=return_url) }}"
onclick=
"return confirm('You sure you want to delete this item?')"
>
<form
class=
"icon"
method=
"POST"
action=
"{{ url_for('.delete_view', id=row.id, return=return_url) }}"
>
<button
onclick=
"return confirm('You sure you want to delete this item?')"
>
<i
class=
"icon-remove"
></i>
</a>
</button>
</form>
{%- endif -%}
</td>
{% for c, name in list_columns %}
...
...
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