Commit 6ed0fa36 authored by Serge S. Koval's avatar Serge S. Koval

Merge branch 'master' of github.com:mrjoes/flask-admin

parents 4e330484 5201bd1c
......@@ -56,6 +56,47 @@ Form Rendering Rule Description
:class:`flask.ext.admin.form.rules.FieldSet` Renders form header and child rules
======================================================= ========================================================
Enabling CSRF Validation
---------------
Flask-Admin does not use Flask-WTF Form class - it uses the wtforms Form class, which does not have CSRF validation.
Adding CSRF validation will require importing flask_wtf and overriding the :class:`flask.ext.admin.form.BaseForm` by using :attr:`flask.ext.admin.model.BaseModelView.form_base_class`::
import os
import flask
**import flask_wtf**
import flask_admin
import flask_sqlalchemy
from flask_admin.contrib.sqla import ModelView
DBFILE = 'app.db'
app = flask.Flask(__name__)
app.config['SECRET_KEY'] = 'Dnit7qz7mfcP0YuelDrF8vLFvk0snhwP'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + DBFILE
**app.config['CSRF_ENABLED'] = True**
**flask_wtf.CsrfProtect(app)**
db = flask_sqlalchemy.SQLAlchemy(app)
admin = flask_admin.Admin(app, name='Admin')
## Here is the fix:
class MyModelView(ModelView):
**form_base_class = flask_wtf.Form**
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String)
password = db.Column(db.String)
if not os.path.exists(DBFILE):
db.create_all()
## The subclass is used here:
admin.add_view( MyModelView(User, db.session, name='User') )
app.run(debug=True)
Further reading
---------------
......
......@@ -292,8 +292,8 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)):
"""
This method will be executed before calling any view method.
By default, it will check if the admin class is accessible and if it is not it will
throw HTTP 404 error.
It will execute the ``inaccessible_callback`` if the view is not
accessible.
:param name:
View function name
......@@ -301,8 +301,18 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)):
View function arguments
"""
if not self.is_accessible():
return self.inaccessible_callback(name, **kwargs)
def inaccessible_callback(self, name, **kwargs):
"""
Handle the response to inaccessible views.
By default, it throw HTTP 403 error. Override this method to
customize the behaviour.
"""
return abort(403)
@property
def _debug(self):
if not self.admin or not self.admin.app:
......
......@@ -423,12 +423,12 @@ class ModelView(BaseModelView):
Verify if the provided column type is text-based.
:returns:
``True`` for ``String``, ``Unicode``, ``Text``, ``UnicodeText``
``True`` for ``String``, ``Unicode``, ``Text``, ``UnicodeText``, ``varchar``
"""
if name:
name = name.lower()
return name in ('string', 'unicode', 'text', 'unicodetext')
return name in ('string', 'unicode', 'text', 'unicodetext', 'varchar')
def scaffold_filters(self, name):
"""
......
......@@ -7,6 +7,10 @@ from flask.ext.admin._compat import text_type, as_unicode
from . import widgets as admin_widgets
"""
An understanding of WTForms's Custom Widgets is helpful for understanding this code: http://wtforms.simplecodes.com/docs/0.6.2/widgets.html#custom-widgets
"""
__all__ = ['DateTimeField', 'TimeField', 'Select2Field', 'Select2TagsField']
class DateTimeField(fields.DateTimeField):
......@@ -14,7 +18,7 @@ class DateTimeField(fields.DateTimeField):
Allows modifying the datetime format of a DateTimeField using form_args.
"""
widget = admin_widgets.DateTimePickerWidget()
def __init__(self, label=None, validators=None, format=None, widget_format=None, **kwargs):
def __init__(self, label=None, validators=None, format=None, **kwargs):
"""
Constructor
......@@ -24,8 +28,6 @@ class DateTimeField(fields.DateTimeField):
Field validators
:param format:
Format for text to date conversion. Defaults to '%Y-%m-%d %H:%M:%S'
:param widget_format:
Widget date format. Defaults to 'yyyy-mm-dd hh:ii:ss'
:param kwargs:
Any additional parameters
"""
......@@ -33,8 +35,6 @@ class DateTimeField(fields.DateTimeField):
self.format = format or '%Y-%m-%d %H:%M:%S'
self.widget_format = widget_format or 'yyyy-mm-dd hh:ii:ss'
class TimeField(fields.Field):
"""
A text field which stores a `datetime.time` object.
......@@ -55,8 +55,6 @@ class TimeField(fields.Field):
Supported time formats, as a enumerable.
:param default_format:
Default time format. Defaults to '%H:%M:%S'
:param widget_format:
Widget date format. Defaults to 'hh:ii:ss'
:param kwargs:
Any additional parameters
"""
......@@ -67,7 +65,6 @@ class TimeField(fields.Field):
'%I:%M:%S %p', '%I:%M %p')
self.default_format = default_format or '%H:%M:%S'
self.widget_format = widget_format or 'hh:ii:ss'
def _value(self):
if self.raw_data:
......
......@@ -237,7 +237,7 @@ class FileUploadField(fields.TextField):
def _save_file(self, data, filename):
path = self._get_path(filename)
if not op.exists(op.dirname(path)):
os.makedirs(os.path.dirname(path), self.permission)
os.makedirs(os.path.dirname(path), self.permission | 0o111)
data.save(path)
......
......@@ -3,9 +3,7 @@ from flask.globals import _request_ctx_stack
from flask.ext.admin.babel import gettext, ngettext
from flask.ext.admin import helpers as h
__all__ = ['Select2Widget', 'DatePickerWidget', 'DateTimePickerWidget', 'RenderTemplateWidget',
'Select2TagsWidget', ]
__all__ = ['Select2Widget', 'DatePickerWidget', 'DateTimePickerWidget', 'RenderTemplateWidget', 'Select2TagsWidget', ]
class Select2Widget(widgets.Select):
"""
......@@ -15,10 +13,9 @@ class Select2Widget(widgets.Select):
work.
"""
def __call__(self, field, **kwargs):
allow_blank = getattr(field, 'allow_blank', False)
kwargs['data-role'] = u'select2'
kwargs.setdefault('data-role', u'select2')
allow_blank = getattr(field, 'allow_blank', False)
if allow_blank and not self.multiple:
kwargs['data-allow-blank'] = u'1'
......@@ -30,8 +27,8 @@ class Select2TagsWidget(widgets.TextInput):
You must include select2.js, form.js and select2 stylesheet for it to work.
"""
def __call__(self, field, **kwargs):
kwargs['data-role'] = u'select2'
kwargs['data-tags'] = u'1'
kwargs.setdefault('data-role', u'select2')
kwargs.setdefault('data-tags', u'1')
return super(Select2TagsWidget, self).__call__(field, **kwargs)
......@@ -43,9 +40,10 @@ class DatePickerWidget(widgets.TextInput):
You must include bootstrap-datepicker.js and form.js for styling to work.
"""
def __call__(self, field, **kwargs):
kwargs['data-role'] = u'datepicker'
kwargs['data-date-format'] = u'yyyy-mm-dd'
kwargs['data-date-autoclose'] = u'true'
kwargs.setdefault('data-role', u'datepicker')
kwargs.setdefault('data-date-format', u'yyyy-mm-dd')
kwargs.setdefault('data-date-autoclose', u'true')
self.date_format = kwargs['data-date-format']
return super(DatePickerWidget, self).__call__(field, **kwargs)
......@@ -56,11 +54,11 @@ class DateTimePickerWidget(widgets.TextInput):
You must include bootstrap-datepicker.js and form.js for styling to work.
"""
def __call__(self, field, **kwargs):
kwargs['data-role'] = u'datetimepicker'
kwargs['data-date-format'] = field.widget_format or u'yyyy-mm-dd hh:ii:ss'
kwargs['data-date-autoclose'] = u'true'
kwargs['data-date-today-btn'] = u'linked'
kwargs['data-date-today-highlight'] = u'true'
kwargs.setdefault('data-role', u'datetimepicker')
kwargs.setdefault('data-date-format', u'yyyy-mm-dd hh:ii:ss')
kwargs.setdefault('data-date-autoclose', u'true')
kwargs.setdefault('data-date-today-btn', u'linked')
kwargs.setdefault('data-date-today-highlight', u'true')
return super(DateTimePickerWidget, self).__call__(field, **kwargs)
......@@ -71,9 +69,9 @@ class TimePickerWidget(widgets.TextInput):
You must include bootstrap-datepicker.js and form.js for styling to work.
"""
def __call__(self, field, **kwargs):
kwargs['data-role'] = u'timepicker'
kwargs['data-date-format'] = field.widget_format or 'hh:ii:ss'
kwargs['data-date-autoclose'] = u'true'
kwargs.setdefault('data-role', u'timepicker')
kwargs.setdefault('data-date-format', u'hh:ii:ss')
kwargs.setdefault('data-date-autoclose', u'true')
return super(TimePickerWidget, self).__call__(field, **kwargs)
......
......@@ -365,6 +365,15 @@ class BaseModelView(BaseView, ActionsMixin):
'style': 'color: black'
}
}
Note, changing the format of a DateTimeField will require changes to both form_widget_args and form_args::
form_args = dict(
start=dict(format='%Y-%m-%d %I:%M %p') # changes how the input is parsed by strptime (12 hour time)
)
form_widget_args = dict(
start={'data-date-format': u'yyyy-mm-dd HH:ii P', 'data-show-meridian': 'True'} # changes how the DateTimeField displays the time
)
"""
form_extra_fields = None
......
from nose.tools import ok_, eq_, raises
from flask import Flask, request
from flask import Flask, request, abort
from flask.views import MethodView
from flask.ext.admin import base
......@@ -232,6 +232,20 @@ def test_permissions():
eq_(rv.status_code, 403)
def test_inaccessible_callback():
app = Flask(__name__)
admin = base.Admin(app)
view = MockView()
admin.add_view(view)
client = app.test_client()
view.allow_access = False
view.inaccessible_callback = lambda *args, **kwargs: abort(418)
rv = client.get('/admin/mockview/')
eq_(rv.status_code, 418)
def get_visibility():
app = Flask(__name__)
admin = base.Admin(app)
......
This diff is collapsed.
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