Commit b22b3dd1 authored by Paul Brown's avatar Paul Brown

improve fileadmin flexibility by moving forms into functions, fix CSRF, and...

improve fileadmin flexibility by moving forms into functions, fix CSRF, and fix error messages on validation failure
parent 6b9d4fdf
...@@ -9,7 +9,7 @@ msgid "" ...@@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Flask-Admin VERSION\n" "Project-Id-Version: Flask-Admin VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-01-16 12:12-0600\n" "POT-Creation-Date: 2015-02-28 21:53-0600\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -18,153 +18,159 @@ msgstr "" ...@@ -18,153 +18,159 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n" "Generated-By: Babel 1.3\n"
#: ../flask_admin/base.py:419 #: ../flask_admin/base.py:426
msgid "Home" msgid "Home"
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:34 #: ../flask_admin/contrib/fileadmin.py:220
msgid "Invalid directory name"
msgstr ""
#: ../flask_admin/contrib/fileadmin.py:42
msgid "File to upload" msgid "File to upload"
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:51 #: ../flask_admin/contrib/fileadmin.py:228
msgid "File required." msgid "File required."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:56 #: ../flask_admin/contrib/fileadmin.py:233
msgid "Invalid file type." msgid "Invalid file type."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:60 #: ../flask_admin/contrib/fileadmin.py:244
msgid "Content" msgid "Content"
msgstr "" 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 #, python-format
msgid "File \"%(name)s\" already exists." msgid "File \"%(name)s\" already exists."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:446 #: ../flask_admin/contrib/fileadmin.py:568
#: ../flask_admin/contrib/fileadmin.py:512 #: ../flask_admin/contrib/fileadmin.py:635
#: ../flask_admin/contrib/fileadmin.py:565 #: ../flask_admin/contrib/fileadmin.py:688
#: ../flask_admin/contrib/fileadmin.py:602 #: ../flask_admin/contrib/fileadmin.py:729
#: ../flask_admin/contrib/fileadmin.py:645 #: ../flask_admin/contrib/fileadmin.py:775
#: ../flask_admin/contrib/fileadmin.py:693 #: ../flask_admin/contrib/fileadmin.py:824
msgid "Permission denied." msgid "Permission denied."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:508 #: ../flask_admin/contrib/fileadmin.py:631
msgid "File uploading is disabled." msgid "File uploading is disabled."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:521 #: ../flask_admin/contrib/fileadmin.py:644
#, python-format #, python-format
msgid "Failed to save file: %(error)s" msgid "Failed to save file: %(error)s"
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:561 #: ../flask_admin/contrib/fileadmin.py:684
msgid "Directory creation is disabled." msgid "Directory creation is disabled."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:576 #: ../flask_admin/contrib/fileadmin.py:699
#, python-format #, python-format
msgid "Failed to create directory: %(error)s" msgid "Failed to create directory: %(error)s"
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:598 #: ../flask_admin/contrib/fileadmin.py:725
msgid "Deletion is disabled." msgid "Deletion is disabled."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:607 #: ../flask_admin/contrib/fileadmin.py:734
msgid "Directory deletion is disabled." msgid "Directory deletion is disabled."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:613 #: ../flask_admin/contrib/fileadmin.py:740
#, python-format #, python-format
msgid "Directory \"%(path)s\" was successfully deleted." msgid "Directory \"%(path)s\" was successfully deleted."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:615 #: ../flask_admin/contrib/fileadmin.py:742
#, python-format #, python-format
msgid "Failed to delete directory: %(error)s" msgid "Failed to delete directory: %(error)s"
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:620 #: ../flask_admin/contrib/fileadmin.py:747
#: ../flask_admin/contrib/fileadmin.py:759 #: ../flask_admin/contrib/fileadmin.py:892
#, python-format #, python-format
msgid "File \"%(name)s\" was successfully deleted." msgid "File \"%(name)s\" was successfully deleted."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:622 #: ../flask_admin/contrib/fileadmin.py:749
#: ../flask_admin/contrib/fileadmin.py:761 #: ../flask_admin/contrib/fileadmin.py:894
#, python-format #, python-format
msgid "Failed to delete file: %(name)s" msgid "Failed to delete file: %(name)s"
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:641 #: ../flask_admin/contrib/fileadmin.py:771
msgid "Renaming is disabled." msgid "Renaming is disabled."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:649 #: ../flask_admin/contrib/fileadmin.py:779
msgid "Path does not exist." msgid "Path does not exist."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:661 #: ../flask_admin/contrib/fileadmin.py:790
#, python-format #, python-format
msgid "Successfully renamed \"%(src)s\" to \"%(dst)s\"" msgid "Successfully renamed \"%(src)s\" to \"%(dst)s\""
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:664 #: ../flask_admin/contrib/fileadmin.py:793
#, python-format #, python-format
msgid "Failed to rename: %(error)s" msgid "Failed to rename: %(error)s"
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:709 #: ../flask_admin/contrib/fileadmin.py:840
#, python-format #, python-format
msgid "Error saving changes to %(name)s." msgid "Error saving changes to %(name)s."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:713 #: ../flask_admin/contrib/fileadmin.py:844
#, python-format #, python-format
msgid "Changes to %(name)s saved successfully." msgid "Changes to %(name)s saved successfully."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:720 #: ../flask_admin/contrib/fileadmin.py:853
#, python-format #, python-format
msgid "Error reading %(name)s." msgid "Error reading %(name)s."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:723 #: ../flask_admin/contrib/fileadmin.py:856
#: ../flask_admin/contrib/fileadmin.py:732 #: ../flask_admin/contrib/fileadmin.py:865
#, python-format #, python-format
msgid "Unexpected error while reading from %(name)s" msgid "Unexpected error while reading from %(name)s"
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:729 #: ../flask_admin/contrib/fileadmin.py:862
#, python-format #, python-format
msgid "Cannot edit %(name)s." msgid "Cannot edit %(name)s."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:746 #: ../flask_admin/contrib/fileadmin.py:879
#: ../flask_admin/contrib/mongoengine/view.py:612 #: ../flask_admin/contrib/mongoengine/view.py:625
#: ../flask_admin/contrib/peewee/view.py:418 #: ../flask_admin/contrib/peewee/view.py:429
#: ../flask_admin/contrib/pymongo/view.py:343 #: ../flask_admin/contrib/pymongo/view.py:348
#: ../flask_admin/contrib/sqla/view.py:945 #: ../flask_admin/contrib/sqla/view.py:974
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:747 #: ../flask_admin/contrib/fileadmin.py:880
msgid "Are you sure you want to delete these files?" msgid "Are you sure you want to delete these files?"
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:750 #: ../flask_admin/contrib/fileadmin.py:883
msgid "File deletion is disabled." msgid "File deletion is disabled."
msgstr "" msgstr ""
#: ../flask_admin/contrib/fileadmin.py:763 #: ../flask_admin/contrib/fileadmin.py:896
msgid "Edit" msgid "Edit"
msgstr "" msgstr ""
...@@ -246,57 +252,57 @@ msgstr "" ...@@ -246,57 +252,57 @@ msgstr ""
msgid "not between" msgid "not between"
msgstr "" msgstr ""
#: ../flask_admin/contrib/mongoengine/view.py:506 #: ../flask_admin/contrib/mongoengine/view.py:519
#, python-format #, python-format
msgid "Failed to get model. %(error)s" msgid "Failed to get model. %(error)s"
msgstr "" msgstr ""
#: ../flask_admin/contrib/mongoengine/view.py:525 #: ../flask_admin/contrib/mongoengine/view.py:538
#: ../flask_admin/contrib/peewee/view.py:369 #: ../flask_admin/contrib/peewee/view.py:380
#: ../flask_admin/contrib/pymongo/view.py:278 #: ../flask_admin/contrib/pymongo/view.py:283
#: ../flask_admin/contrib/sqla/view.py:877 #: ../flask_admin/contrib/sqla/view.py:906
#, python-format #, python-format
msgid "Failed to create record. %(error)s" msgid "Failed to create record. %(error)s"
msgstr "" msgstr ""
#: ../flask_admin/contrib/mongoengine/view.py:551 #: ../flask_admin/contrib/mongoengine/view.py:564
#: ../flask_admin/contrib/peewee/view.py:388 #: ../flask_admin/contrib/peewee/view.py:399
#: ../flask_admin/contrib/pymongo/view.py:303 #: ../flask_admin/contrib/pymongo/view.py:308
#: ../flask_admin/contrib/sqla/view.py:903 ../flask_admin/model/base.py:1563 #: ../flask_admin/contrib/sqla/view.py:932 ../flask_admin/model/base.py:1613
#: ../flask_admin/model/base.py:1572 #: ../flask_admin/model/base.py:1622
#, python-format #, python-format
msgid "Failed to update record. %(error)s" msgid "Failed to update record. %(error)s"
msgstr "" msgstr ""
#: ../flask_admin/contrib/mongoengine/view.py:575 #: ../flask_admin/contrib/mongoengine/view.py:588
#: ../flask_admin/contrib/peewee/view.py:404 #: ../flask_admin/contrib/peewee/view.py:415
#: ../flask_admin/contrib/pymongo/view.py:329 #: ../flask_admin/contrib/pymongo/view.py:334
#: ../flask_admin/contrib/sqla/view.py:929 ../flask_admin/model/base.py:1513 #: ../flask_admin/contrib/sqla/view.py:958
#, python-format #, python-format
msgid "Failed to delete record. %(error)s" msgid "Failed to delete record. %(error)s"
msgstr "" msgstr ""
#: ../flask_admin/contrib/mongoengine/view.py:613 #: ../flask_admin/contrib/mongoengine/view.py:626
#: ../flask_admin/contrib/peewee/view.py:419 #: ../flask_admin/contrib/peewee/view.py:430
#: ../flask_admin/contrib/pymongo/view.py:344 #: ../flask_admin/contrib/pymongo/view.py:349
#: ../flask_admin/contrib/sqla/view.py:946 #: ../flask_admin/contrib/sqla/view.py:975
msgid "Are you sure you want to delete selected records?" msgid "Are you sure you want to delete selected records?"
msgstr "" msgstr ""
#: ../flask_admin/contrib/mongoengine/view.py:622 #: ../flask_admin/contrib/mongoengine/view.py:635
#: ../flask_admin/contrib/peewee/view.py:435 #: ../flask_admin/contrib/peewee/view.py:446
#: ../flask_admin/contrib/pymongo/view.py:354 #: ../flask_admin/contrib/pymongo/view.py:359
#: ../flask_admin/contrib/sqla/view.py:962 ../flask_admin/model/base.py:1506 #: ../flask_admin/contrib/sqla/view.py:991 ../flask_admin/model/base.py:1561
#, python-format #, python-format
msgid "Record was successfully deleted." msgid "Record was successfully deleted."
msgid_plural "%(count)s records were successfully deleted." msgid_plural "%(count)s records were successfully deleted."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: ../flask_admin/contrib/mongoengine/view.py:628 #: ../flask_admin/contrib/mongoengine/view.py:641
#: ../flask_admin/contrib/peewee/view.py:441 #: ../flask_admin/contrib/peewee/view.py:452
#: ../flask_admin/contrib/pymongo/view.py:359 #: ../flask_admin/contrib/pymongo/view.py:364
#: ../flask_admin/contrib/sqla/view.py:970 #: ../flask_admin/contrib/sqla/view.py:999
#, python-format #, python-format
msgid "Failed to delete records. %(error)s" msgid "Failed to delete records. %(error)s"
msgstr "" msgstr ""
...@@ -319,7 +325,7 @@ msgid_plural "At least %d items are required" ...@@ -319,7 +325,7 @@ msgid_plural "At least %d items are required"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: ../flask_admin/contrib/sqla/view.py:856 #: ../flask_admin/contrib/sqla/view.py:885
#, python-format #, python-format
msgid "Integrity error. %(message)s" msgid "Integrity error. %(message)s"
msgstr "" msgstr ""
...@@ -336,20 +342,20 @@ msgstr "" ...@@ -336,20 +342,20 @@ msgstr ""
msgid "Invalid file extension" msgid "Invalid file extension"
msgstr "" msgstr ""
#: ../flask_admin/model/base.py:1173 #: ../flask_admin/model/base.py:1227
msgid "There are no items in the table." msgid "There are no items in the table."
msgstr "" msgstr ""
#: ../flask_admin/model/base.py:1197 #: ../flask_admin/model/base.py:1251
#, python-format #, python-format
msgid "Invalid Filter Value: %(value)s" msgid "Invalid Filter Value: %(value)s"
msgstr "" msgstr ""
#: ../flask_admin/model/base.py:1430 #: ../flask_admin/model/base.py:1484
msgid "Record was successfully created." msgid "Record was successfully created."
msgstr "" msgstr ""
#: ../flask_admin/model/base.py:1467 ../flask_admin/model/base.py:1568 #: ../flask_admin/model/base.py:1521 ../flask_admin/model/base.py:1618
msgid "Record was successfully saved." msgid "Record was successfully saved."
msgstr "" msgstr ""
...@@ -392,40 +398,35 @@ msgstr "" ...@@ -392,40 +398,35 @@ msgstr ""
msgid "Root" msgid "Root"
msgstr "" 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/bootstrap2/admin/file/list.html:36
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:36 #: ../flask_admin/templates/bootstrap3/admin/file/list.html:36
msgid "Size" msgid "Size"
msgstr "" msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:62 #: ../flask_admin/templates/bootstrap2/admin/file/list.html:63
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:62 #: ../flask_admin/templates/bootstrap3/admin/file/list.html:63
#, python-format #, python-format
msgid "Are you sure you want to delete \\'%(name)s\\' recursively?" msgid "Are you sure you want to delete \\'%(name)s\\' recursively?"
msgstr "" msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:70 #: ../flask_admin/templates/bootstrap2/admin/file/list.html:72
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:70 #: ../flask_admin/templates/bootstrap3/admin/file/list.html:72
#, python-format #, python-format
msgid "Are you sure you want to delete \\'%(name)s\\'?" msgid "Are you sure you want to delete \\'%(name)s\\'?"
msgstr "" msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:105 #: ../flask_admin/templates/bootstrap2/admin/file/list.html:107
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:105 #: ../flask_admin/templates/bootstrap3/admin/file/list.html:107
msgid "Upload File" msgid "Upload File"
msgstr "" msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:110 #: ../flask_admin/templates/bootstrap2/admin/file/list.html:112
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:110 #: ../flask_admin/templates/bootstrap3/admin/file/list.html:112
msgid "Create Directory" msgid "Create Directory"
msgstr "" msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/file/list.html:127 #: ../flask_admin/templates/bootstrap2/admin/file/list.html:129
#: ../flask_admin/templates/bootstrap3/admin/file/list.html:127 #: ../flask_admin/templates/bootstrap3/admin/file/list.html:129
msgid "Please select at least one file." msgid "Please select at least one file."
msgstr "" msgstr ""
...@@ -519,17 +520,17 @@ msgstr "" ...@@ -519,17 +520,17 @@ msgstr ""
msgid "Edit record" msgid "Edit record"
msgstr "" msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/model/list.html:116 #: ../flask_admin/templates/bootstrap2/admin/model/list.html:114
#: ../flask_admin/templates/bootstrap3/admin/model/list.html:116 #: ../flask_admin/templates/bootstrap3/admin/model/list.html:114
msgid "Are you sure you want to delete this record?" msgid "Are you sure you want to delete this record?"
msgstr "" msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/model/list.html:116 #: ../flask_admin/templates/bootstrap2/admin/model/list.html:114
msgid "Delete record" msgid "Delete record"
msgstr "" msgstr ""
#: ../flask_admin/templates/bootstrap2/admin/model/list.html:161 #: ../flask_admin/templates/bootstrap2/admin/model/list.html:159
#: ../flask_admin/templates/bootstrap3/admin/model/list.html:160 #: ../flask_admin/templates/bootstrap3/admin/model/list.html:158
msgid "Please select at least one record." msgid "Please select at least one record."
msgstr "" msgstr ""
...@@ -19,48 +19,6 @@ from flask.ext.admin.actions import action, ActionsMixin ...@@ -19,48 +19,6 @@ from flask.ext.admin.actions import action, ActionsMixin
from flask.ext.admin.babel import gettext, lazy_gettext 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): class FileAdmin(BaseView, ActionsMixin):
""" """
Simple file-management interface. Simple file-management interface.
...@@ -74,11 +32,15 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -74,11 +32,15 @@ class FileAdmin(BaseView, ActionsMixin):
Sample usage:: Sample usage::
import os.path as op
from flask.ext.admin import Admin
from flask.ext.admin.contrib.fileadmin import FileAdmin
admin = Admin() admin = Admin()
path = op.join(op.dirname(__file__), 'static') path = op.join(op.dirname(__file__), 'static')
admin.add_view(FileAdmin(path, '/static/', name='Static Files')) admin.add_view(FileAdmin(path, '/static/', name='Static Files'))
admin.setup_app(app)
""" """
can_upload = True can_upload = True
...@@ -156,9 +118,22 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -156,9 +118,22 @@ class FileAdmin(BaseView, ActionsMixin):
Edit template Edit template
""" """
upload_form = UploadForm form_base_class = form.BaseForm
""" """
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, def __init__(self, base_path, base_url=None,
...@@ -231,6 +206,139 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -231,6 +206,139 @@ class FileAdmin(BaseView, ActionsMixin):
""" """
return self.base_url 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): def is_file_allowed(self, filename):
""" """
Verify if file can be uploaded. Verify if file can be uploaded.
...@@ -291,6 +399,15 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -291,6 +399,15 @@ class FileAdmin(BaseView, ActionsMixin):
""" """
file_data.save(path) 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): def _get_dir_url(self, endpoint, path=None, **kwargs):
""" """
Return prettified URL Return prettified URL
...@@ -439,11 +556,16 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -439,11 +556,16 @@ class FileAdmin(BaseView, ActionsMixin):
:param path: :param path:
Optional directory path. If not provided, will use the base directory 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 # Get path and verify if it is valid
base_path, directory, path = self._normalize_path(path) base_path, directory, path = self._normalize_path(path)
if not self.is_accessible_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')) return redirect(self._get_dir_url('.index'))
# Get directory listing # Get directory listing
...@@ -490,7 +612,8 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -490,7 +612,8 @@ class FileAdmin(BaseView, ActionsMixin):
get_file_url=self._get_file_url, get_file_url=self._get_file_url,
items=items, items=items,
actions=actions, actions=actions,
actions_confirmation=actions_confirmation) actions_confirmation=actions_confirmation,
delete_form=delete_form)
@expose('/upload/', methods=('GET', 'POST')) @expose('/upload/', methods=('GET', 'POST'))
@expose('/upload/<path:path>', methods=('GET', 'POST')) @expose('/upload/<path:path>', methods=('GET', 'POST'))
...@@ -509,16 +632,16 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -509,16 +632,16 @@ class FileAdmin(BaseView, ActionsMixin):
return redirect(self._get_dir_url('.index', path)) return redirect(self._get_dir_url('.index', path))
if not self.is_accessible_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')) return redirect(self._get_dir_url('.index'))
form = self.upload_form(self) form = self.upload_form()
if helpers.validate_form_on_submit(form): if self.validate_form(form):
try: try:
self._save_form_files(directory, path, form) self._save_form_files(directory, path, form)
return redirect(self._get_dir_url('.index', path)) return redirect(self._get_dir_url('.index', path))
except Exception as ex: 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) return self.render(self.upload_template, form=form)
...@@ -562,18 +685,20 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -562,18 +685,20 @@ class FileAdmin(BaseView, ActionsMixin):
return redirect(dir_url) return redirect(dir_url)
if not self.is_accessible_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')) 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: try:
os.mkdir(op.join(directory, form.name.data)) os.mkdir(op.join(directory, form.name.data))
self.on_mkdir(directory, form.name.data) self.on_mkdir(directory, form.name.data)
return redirect(dir_url) return redirect(dir_url)
except Exception as ex: except Exception as ex:
flash(gettext('Failed to create directory: %(error)s', error=ex), 'error') 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, return self.render(self.mkdir_template,
form=form, form=form,
...@@ -584,27 +709,29 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -584,27 +709,29 @@ class FileAdmin(BaseView, ActionsMixin):
""" """
Delete view method Delete view method
""" """
path = request.form.get('path') form = self.delete_form()
if not path: path = form.path.data
return redirect(self.get_url('.index')) if path:
return_url = self._get_dir_url('.index', op.dirname(path))
else:
return_url = self.get_url('.index')
if self.validate_form(form):
# Get path and verify if it is valid # Get path and verify if it is valid
base_path, full_path, path = self._normalize_path(path) base_path, full_path, path = self._normalize_path(path)
return_url = self._get_dir_url('.index', op.dirname(path))
if not self.can_delete: if not self.can_delete:
flash(gettext('Deletion is disabled.')) flash(gettext('Deletion is disabled.'), 'error')
return redirect(return_url) return redirect(return_url)
if not self.is_accessible_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')) return redirect(self._get_dir_url('.index'))
if op.isdir(full_path): if op.isdir(full_path):
if not self.can_delete_dirs: if not self.can_delete_dirs:
flash(gettext('Directory deletion is disabled.')) flash(gettext('Directory deletion is disabled.'), 'error')
return redirect(return_url) return redirect(return_url)
try: try:
...@@ -620,6 +747,8 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -620,6 +747,8 @@ class FileAdmin(BaseView, ActionsMixin):
flash(gettext('File "%(name)s" was successfully deleted.', name=path)) flash(gettext('File "%(name)s" was successfully deleted.', name=path))
except Exception as ex: except Exception as ex:
flash(gettext('Failed to delete file: %(name)s', name=ex), 'error') flash(gettext('Failed to delete file: %(name)s', name=ex), 'error')
else:
helpers.flash_errors(form, message='Failed to delete file. %(error)s')
return redirect(return_url) return redirect(return_url)
...@@ -628,29 +757,29 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -628,29 +757,29 @@ class FileAdmin(BaseView, ActionsMixin):
""" """
Rename view method Rename view method
""" """
path = request.args.get('path') form = self.name_form()
if not path:
return redirect(self.get_url('.index'))
path = form.path.data
if path:
base_path, full_path, path = self._normalize_path(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: if not self.can_rename:
flash(gettext('Renaming is disabled.')) flash(gettext('Renaming is disabled.'), 'error')
return redirect(return_url) return redirect(return_url)
if not self.is_accessible_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')) return redirect(self._get_dir_url('.index'))
if not op.exists(full_path): if not op.exists(full_path):
flash(gettext('Path does not exist.')) flash(gettext('Path does not exist.'), 'error')
return redirect(return_url) return redirect(return_url)
form = NameForm(helpers.get_form_data(), name=op.basename(path)) if self.validate_form(form):
if helpers.validate_form_on_submit(form):
try: try:
dir_base = op.dirname(full_path) dir_base = op.dirname(full_path)
filename = secure_filename(form.name.data) filename = secure_filename(form.name.data)
...@@ -664,6 +793,8 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -664,6 +793,8 @@ class FileAdmin(BaseView, ActionsMixin):
flash(gettext('Failed to rename: %(error)s', error=ex), 'error') flash(gettext('Failed to rename: %(error)s', error=ex), 'error')
return redirect(return_url) return redirect(return_url)
else:
helpers.flash_errors(form, message='Failed to rename: %(error)s')
return self.render(self.rename_template, return self.render(self.rename_template,
form=form, form=form,
...@@ -690,16 +821,16 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -690,16 +821,16 @@ class FileAdmin(BaseView, ActionsMixin):
base_path, full_path, path = self._normalize_path(path) base_path, full_path, path = self._normalize_path(path)
if not self.is_accessible_path(path) or not self.is_file_editable(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')) return redirect(self._get_dir_url('.index'))
dir_url = self._get_dir_url('.index', os.path.dirname(path)) dir_url = self._get_dir_url('.index', os.path.dirname(path))
next_url = next_url or dir_url next_url = next_url or dir_url
form = EditForm(helpers.get_form_data()) form = self.edit_form()
error = False error = False
if helpers.validate_form_on_submit(form): if self.validate_form(form):
form.process(request.form, content='') form.process(request.form, content='')
if form.validate(): if form.validate():
try: try:
...@@ -713,6 +844,8 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -713,6 +844,8 @@ class FileAdmin(BaseView, ActionsMixin):
flash(gettext("Changes to %(name)s saved successfully.", name=path)) flash(gettext("Changes to %(name)s saved successfully.", name=path))
return redirect(next_url) return redirect(next_url)
else: else:
helpers.flash_errors(form, message='Failed to edit file. %(error)s')
try: try:
with open(full_path, 'r') as f: with open(full_path, 'r') as f:
content = f.read() content = f.read()
......
...@@ -58,7 +58,8 @@ ...@@ -58,7 +58,8 @@
{% if is_dir %} {% if is_dir %}
{% if name != '..' and admin_view.can_delete_dirs %} {% if name != '..' and admin_view.can_delete_dirs %}
<form class="icon" method="POST" action="{{ get_url('.delete') }}"> <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) }}')"> <button onclick="return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\' recursively?', name=name) }}')">
<i class="icon-remove"></i> <i class="icon-remove"></i>
</button> </button>
...@@ -66,7 +67,8 @@ ...@@ -66,7 +67,8 @@
{% endif %} {% endif %}
{% else %} {% else %}
<form class="icon" method="POST" action="{{ get_url('.delete') }}"> <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) }}')"> <button onclick="return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\'?', name=name) }}')">
<i class="icon-remove"></i> <i class="icon-remove"></i>
</button> </button>
......
...@@ -58,7 +58,8 @@ ...@@ -58,7 +58,8 @@
{% if is_dir %} {% if is_dir %}
{% if name != '..' and admin_view.can_delete_dirs %} {% if name != '..' and admin_view.can_delete_dirs %}
<form class="icon" method="POST" action="{{ get_url('.delete') }}"> <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) }}')"> <button onclick="return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\' recursively?', name=name) }}')">
<i class="glyphicon glyphicon-remove"></i> <i class="glyphicon glyphicon-remove"></i>
</button> </button>
...@@ -66,7 +67,8 @@ ...@@ -66,7 +67,8 @@
{% endif %} {% endif %}
{% else %} {% else %}
<form class="icon" method="POST" action="{{ get_url('.delete') }}"> <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) }}')"> <button onclick="return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\'?', name=name) }}')">
<i class="glyphicon glyphicon-trash"></i> <i class="glyphicon glyphicon-trash"></i>
</button> </button>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment