Commit 18267649 authored by Sergey Markelov's avatar Sergey Markelov

Merge remote-tracking branch 'remotes/upstream/master'

parents 26742fb6 cfeaa030
...@@ -292,8 +292,8 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)): ...@@ -292,8 +292,8 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)):
""" """
This method will be executed before calling any view method. 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 It will execute the ``inaccessible_callback`` if the view is not
throw HTTP 404 error. accessible.
:param name: :param name:
View function name View function name
...@@ -301,7 +301,17 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)): ...@@ -301,7 +301,17 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)):
View function arguments View function arguments
""" """
if not self.is_accessible(): if not self.is_accessible():
return abort(403) 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 @property
def _debug(self): def _debug(self):
......
...@@ -334,8 +334,8 @@ class ModelView(BaseModelView): ...@@ -334,8 +334,8 @@ class ModelView(BaseModelView):
# TODO: Optimize me # TODO: Optimize me
for pk in ids: for pk in ids:
self.coll.remove({'_id': self._get_valid_id(pk)}) if self.delete_model(self.get_one(pk)):
count += 1 count += 1
flash(ngettext('Model was successfully deleted.', flash(ngettext('Model was successfully deleted.',
'%(count)s models were successfully deleted.', '%(count)s models were successfully deleted.',
......
...@@ -333,6 +333,9 @@ class ModelView(BaseModelView): ...@@ -333,6 +333,9 @@ class ModelView(BaseModelView):
else: else:
column = p.columns[0] column = p.columns[0]
if column.foreign_keys:
continue
if not self.column_display_pk and column.primary_key: if not self.column_display_pk and column.primary_key:
continue continue
...@@ -420,12 +423,12 @@ class ModelView(BaseModelView): ...@@ -420,12 +423,12 @@ class ModelView(BaseModelView):
Verify if the provided column type is text-based. Verify if the provided column type is text-based.
:returns: :returns:
``True`` for ``String``, ``Unicode``, ``Text``, ``UnicodeText`` ``True`` for ``String``, ``Unicode``, ``Text``, ``UnicodeText``, ``varchar``
""" """
if name: if name:
name = name.lower() name = name.lower()
return name in ('string', 'unicode', 'text', 'unicodetext') return name in ('string', 'unicode', 'text', 'unicodetext', 'varchar')
def scaffold_filters(self, name): def scaffold_filters(self, name):
""" """
...@@ -605,6 +608,11 @@ class ModelView(BaseModelView): ...@@ -605,6 +608,11 @@ class ModelView(BaseModelView):
def get_count_query(self): def get_count_query(self):
""" """
Return a the count query for the model type Return a the count query for the model type
A query(self.model).count() approach produces an excessive
subquery, so query(func.count('*')) should be used instead.
See #45a2723 commit message for details.
""" """
return self.session.query(func.count('*')).select_from(self.model) return self.session.query(func.count('*')).select_from(self.model)
...@@ -783,7 +791,7 @@ class ModelView(BaseModelView): ...@@ -783,7 +791,7 @@ class ModelView(BaseModelView):
flash(gettext('Integrity error. %(message)s', message=exc.message), 'error') flash(gettext('Integrity error. %(message)s', message=exc.message), 'error')
return True return True
return super(BaseModelView, self).handle_view_exception(exc) return super(ModelView, self).handle_view_exception(exc)
# Model handlers # Model handlers
def create_model(self, form): def create_model(self, form):
...@@ -882,8 +890,8 @@ class ModelView(BaseModelView): ...@@ -882,8 +890,8 @@ class ModelView(BaseModelView):
count = 0 count = 0
for m in query.all(): for m in query.all():
self.session.delete(m) if self.delete_model(m):
count += 1 count += 1
self.session.commit() self.session.commit()
......
...@@ -181,9 +181,10 @@ class FileUploadField(fields.StringField): ...@@ -181,9 +181,10 @@ class FileUploadField(fields.StringField):
map(lambda x: x.lower(), self.allowed_extensions)) map(lambda x: x.lower(), self.allowed_extensions))
def pre_validate(self, form): def pre_validate(self, form):
if (self.data and if (self.data
isinstance(self.data, FileStorage) and and self.data.filename
not self.is_file_allowed(self.data.filename)): and isinstance(self.data, FileStorage)
and not self.is_file_allowed(self.data.filename)):
raise ValidationError(gettext('Invalid file extension')) raise ValidationError(gettext('Invalid file extension'))
def process(self, formdata, data=unset_value): def process(self, formdata, data=unset_value):
...@@ -237,7 +238,7 @@ class FileUploadField(fields.StringField): ...@@ -237,7 +238,7 @@ class FileUploadField(fields.StringField):
def _save_file(self, data, filename): def _save_file(self, data, filename):
path = self._get_path(filename) path = self._get_path(filename)
if not op.exists(op.dirname(path)): 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) data.save(path)
...@@ -355,7 +356,9 @@ class ImageUploadField(FileUploadField): ...@@ -355,7 +356,9 @@ class ImageUploadField(FileUploadField):
def pre_validate(self, form): def pre_validate(self, form):
super(ImageUploadField, self).pre_validate(form) super(ImageUploadField, self).pre_validate(form)
if self.data and isinstance(self.data, FileStorage): if (self.data and
isinstance(self.data, FileStorage) and
self.data.filename):
try: try:
self.image = Image.open(self.data) self.image = Image.open(self.data)
except Exception as e: except Exception as e:
......
...@@ -366,13 +366,13 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -366,13 +366,13 @@ class BaseModelView(BaseView, ActionsMixin):
} }
} }
Note, changing the format of a DateTimeField will require changes to both form_widget_args and form_args: Note, changing the format of a DateTimeField will require changes to both form_widget_args and form_args::
form_args = dict( form_args = dict(
start=dict(format='%Y-%m-%d %H:%M') # changes how the input is parsed by strptime start=dict(format='%Y-%m-%d %I:%M %p') # changes how the input is parsed by strptime (12 hour time)
) )
form_widget_args = dict( form_widget_args = dict(
start={'data-date-format': u'yyyy-mm-dd hh:ii'} # changes how the DateTimeField displays the time start={'data-date-format': u'yyyy-mm-dd HH:ii P', 'data-show-meridian': 'True'} # changes how the DateTimeField displays the time
) )
""" """
......
...@@ -140,7 +140,7 @@ ...@@ -140,7 +140,7 @@
var $parentForm = $el.parent().closest('.inline-field'); var $parentForm = $el.parent().closest('.inline-field');
if ($parentForm.length > 0) { if ($parentForm.length > 0 && elID.indexOf($parentForm.attr('id')) !== 0) {
id = $parentForm.attr('id') + '-' + elID; id = $parentForm.attr('id') + '-' + elID;
} }
......
from nose.tools import ok_, eq_, raises 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.views import MethodView
from flask.ext.admin import base from flask.ext.admin import base
...@@ -232,6 +232,20 @@ def test_permissions(): ...@@ -232,6 +232,20 @@ def test_permissions():
eq_(rv.status_code, 403) 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(): def get_visibility():
app = Flask(__name__) app = Flask(__name__)
admin = base.Admin(app) 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