Commit 216a66b6 authored by Serge S. Koval's avatar Serge S. Koval

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

parents e8833279 ed5943b8
......@@ -49,7 +49,7 @@ To run the examples on your local environment, one at a time, do something like:
Documentation
-------------
Flask-Admin is extensively documented, you can find all of the documentation at `http://flask-admin.readthedocs.org/en/latest/ <http://flask-admin.readthedocs.org/en/latest/>`_.
Flask-Admin is extensively documented, you can find all of the documentation at `https://flask-admin.readthedocs.io/en/latest/ <https://flask-admin.readthedocs.io/en/latest/>`_.
The docs are auto-generated from the *.rst* files in the */doc* folder. So if you come across any errors, or
if you think of anything else that should be included, then please make the changes and submit them as a *pull-request*.
......@@ -75,7 +75,7 @@ Or alternatively, you can download the repository and install manually by doing:
Tests
-----
Test are run with *nose*. If you are not familiar with this package you can get some more info from `their website <http://nose.readthedocs.org/>`_.
Test are run with *nose*. If you are not familiar with this package you can get some more info from `their website <https://nose.readthedocs.io/>`_.
To run the tests, from the project directory, simply::
......
......@@ -189,7 +189,7 @@ Managing Geographical Models
If you want to store spatial information in a GIS database, Flask-Admin has
you covered. The GeoAlchemy backend extends the SQLAlchemy backend (just as
`GeoAlchemy <http://geoalchemy-2.readthedocs.org/>`_ extends SQLAlchemy) to give you a pretty and functional map-based
`GeoAlchemy <https://geoalchemy-2.readthedocs.io/>`_ extends SQLAlchemy) to give you a pretty and functional map-based
editor for your admin pages.
Some notable features include:
......@@ -200,7 +200,7 @@ Some notable features include:
interactively using `Leaflet.Draw <https://github.com/Leaflet/Leaflet.draw>`_.
- Graceful fallback: `GeoJSON <http://geojson.org/>`_ data can be edited in a ``<textarea>``, if the
user has turned off Javascript.
- Works with a `Geometry <http://geoalchemy-2.readthedocs.org/en/latest/types.html#geoalchemy2.types.Geometry>`_ SQL field that is integrated with `Shapely <http://toblerity.org/shapely/>`_ objects.
- Works with a `Geometry <https://geoalchemy-2.readthedocs.io/en/latest/types.html#geoalchemy2.types.Geometry>`_ SQL field that is integrated with `Shapely <http://toblerity.org/shapely/>`_ objects.
To get started, define some fields on your model using GeoAlchemy's *Geometry*
field. Next, add model views to your interface using the ModelView class
......@@ -387,7 +387,7 @@ Features:
- GridFS support for file and image uploads
In order to use MongoEngine integration, install the
`Flask-MongoEngine <https://flask-mongoengine.readthedocs.org>`_ package.
`Flask-MongoEngine <https://flask-mongoengine.readthedocs.io>`_ package.
Flask-Admin uses form scaffolding from it.
Known issues:
......@@ -407,7 +407,7 @@ Features:
- Inline editing of related models;
In order to use peewee integration, you need to install two additional Python
packages: `peewee <https://peewee.readthedocs.org/>`_ and `wtf-peewee <https://github.com/coleifer/wtf-peewee/>`_.
packages: `peewee <http://docs.peewee-orm.com/>`_ and `wtf-peewee <https://github.com/coleifer/wtf-peewee/>`_.
Known issues:
......
......@@ -85,7 +85,7 @@ with your database models, and it doesn't require you to write any new view logi
template code. So it's great for when you're deploying something that's still
under development, before you want the whole world to see it.
Have a look at `Flask-BasicAuth <http://flask-basicauth.readthedocs.org/>`_ to see just how
Have a look at `Flask-BasicAuth <https://flask-basicauth.readthedocs.io/>`_ to see just how
easy it is to put your whole application behind HTTP Basic Auth.
Unfortunately, there is no easy way of applying HTTP Basic Auth just to your admin
......@@ -96,7 +96,7 @@ Rolling Your Own
For a more flexible solution, Flask-Admin lets you define access control rules
on each of your admin view classes by simply overriding the `is_accessible` method.
How you implement the logic is up to you, but if you were to use a low-level library like
`Flask-Login <https://flask-login.readthedocs.org/>`_, then restricting access
`Flask-Login <https://flask-login.readthedocs.io/>`_, then restricting access
could be as simple as::
class MicroBlogModelView(sqla.ModelView):
......
......@@ -101,6 +101,7 @@ def security_context_processor():
admin_base_template=admin.base_template,
admin_view=admin.index_view,
h=admin_helpers,
get_url=url_for
)
......
......@@ -21,7 +21,7 @@ db = SQLAlchemy(app)
''' Define a wtforms widget and field.
WTForms documentation on custom widgets:
http://wtforms.readthedocs.org/en/latest/widgets.html#custom-widgets
https://wtforms.readthedocs.io/en/latest/widgets.html#custom-widgets
'''
class CKTextAreaWidget(widgets.TextArea):
def __call__(self, field, **kwargs):
......
from wtforms.fields import TextField
from google.appengine.ext import ndb
import decimal
class GeoPtPropertyField(TextField):
def process_formdata(self, valuelist):
if valuelist:
try:
lat, lon = valuelist[0].split(',')
self.data = ndb.GeoPt(
decimal.Decimal(lat.strip()),
decimal.Decimal(lon.strip())
)
except (decimal.InvalidOperation, ValueError):
raise ValueError('Not a valid coordinate location')
from wtforms_appengine.ndb import ModelConverter
from .fields import GeoPtPropertyField
from flask_admin.model.form import converts
class AdminModelConverter(ModelConverter):
@converts('GeoPt')
def convert_GeoPtProperty(self, model, prop, kwargs):
"""Returns a form field for a ``ndb.GeoPtProperty``."""
return GeoPtPropertyField(**kwargs)
......@@ -7,6 +7,10 @@ from wtforms_appengine import ndb as wt_ndb
from google.appengine.ext import db
from google.appengine.ext import ndb
from flask_wtf import Form
from flask_admin.model.form import create_editable_list_form
from .form import AdminModelConverter
class NdbModelView(BaseModelView):
"""
AppEngine NDB model scaffolding.
......@@ -31,10 +35,46 @@ class NdbModelView(BaseModelView):
#TODO: implement
pass
def scaffold_form(self):
return wt_ndb.model_form(self.model())
form_args = None
def get_list(self, page, sort_field, sort_desc, search, filters):
model_form_converter = AdminModelConverter
"""
Model form conversion class. Use this to implement custom field conversion logic.
For example::
class MyModelConverter(AdminModelConverter):
pass
class MyAdminView(ModelView):
model_form_converter = MyModelConverter
"""
def scaffold_form(self):
form_class = wt_ndb.model_form(
self.model(),
base_class=Form,
only=self.form_columns,
exclude=self.form_excluded_columns,
field_args=self.form_args,
converter=self.model_form_converter(),
)
return form_class
def scaffold_list_form(self, widget=None, validators=None):
form_class = wt_ndb.model_form(
self.model(),
base_class=Form,
only=self.column_editable_list,
field_args=self.form_args,
converter=self.model_form_converter(),
)
result = create_editable_list_form(Form, form_class, widget)
return result
def get_list(self, page, sort_field, sort_desc, search, filters,
page_size=None):
#TODO: implement filters (don't think search can work here)
q = self.model.query()
......@@ -45,7 +85,11 @@ class NdbModelView(BaseModelView):
order_field = -order_field
q = q.order(order_field)
results = q.fetch(self.page_size, offset=page*self.page_size)
if not page_size:
page_size = self.page_size
results = q.fetch(page_size, offset=page*page_size)
return q.count(), results
def get_one(self, urlsafe_key):
......@@ -56,30 +100,35 @@ class NdbModelView(BaseModelView):
model = self.model()
form.populate_obj(model)
model.put()
return model
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
else:
self.after_model_change(form, model, True)
return model
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
else:
self.after_model_change(form, model, False)
return True
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',
......@@ -87,6 +136,10 @@ class NdbModelView(BaseModelView):
# 'error')
logging.exception('Failed to delete record.')
return False
else:
self.after_model_delete(model)
return True
class DbModelView(BaseModelView):
......
......@@ -557,7 +557,8 @@ class InlineModelConverter(InlineModelConverterBase):
info = self.get_info(inline_model)
# Find property from target model to current model
target_mapper = info.model._sa_class_manager.mapper
# Use the base mapper to support inheritance
target_mapper = info.model._sa_class_manager.mapper.base_mapper
reverse_prop = None
......
......@@ -1117,7 +1117,15 @@ class BaseModelView(BaseView, ActionsMixin):
Filter instance
"""
if self.named_filter_urls:
name = ('%s %s' % (flt.name, as_unicode(flt.operation()))).lower()
operation = flt.operation()
try:
# get lazy string original value
operation = operation._args[0]
except AttributeError:
pass
name = ('%s %s' % (flt.name, as_unicode(operation))).lower()
name = filter_char_re.sub('', name)
name = filter_compact_re.sub('_', name)
return name
......
......@@ -98,7 +98,7 @@ class XEditableWidget(object):
"""
Return extra kwargs based on the field type.
"""
if field.type == 'StringField':
if field.type in ['StringField', 'TextField']:
kwargs['data-type'] = 'text'
elif field.type == 'TextAreaField':
kwargs['data-type'] = 'textarea'
......@@ -111,7 +111,7 @@ class XEditableWidget(object):
{'value': '1', 'text': gettext('Yes')}
])
kwargs['data-role'] = 'x-editable-boolean'
elif field.type == 'Select2Field':
elif field.type in ['Select2Field', 'SelectField']:
kwargs['data-type'] = 'select'
choices = [{'value': x, 'text': y} for x, y in field.choices]
......@@ -142,7 +142,7 @@ class XEditableWidget(object):
kwargs['data-type'] = 'number'
kwargs['data-step'] = 'any'
elif field.type in ['QuerySelectField', 'ModelSelectField',
'QuerySelectMultipleField']:
'QuerySelectMultipleField', 'KeyPropertyField']:
# QuerySelectField and ModelSelectField are for relations
kwargs['data-type'] = 'select'
......
......@@ -1834,6 +1834,28 @@ def test_modelview_localization():
for locale in locales:
test_locale(locale)
def test_modelview_named_filter_localization():
app, db, admin = setup()
app.config['BABEL_DEFAULT_LOCALE'] = 'de'
Babel(app)
Model1, _ = create_models(db)
view = CustomModelView(
Model1, db.session,
named_filter_urls=True,
column_filters=['test1'],
)
filters = view.get_filters()
flt = filters[2]
with app.test_request_context():
flt_name = view.get_filter_arg(2, flt)
eq_('test1_equals', flt_name)
def test_custom_form_base():
app, db, admin = setup()
......
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