Commit a81d5d26 authored by Serge S. Koval's avatar Serge S. Koval

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

parents fd80711e 3a13b498
...@@ -14,4 +14,4 @@ venv ...@@ -14,4 +14,4 @@ venv
*.sqlite *.sqlite
*.sublime-* *.sublime-*
.coverage .coverage
__pycache__
- Core - Python 3
- View Site button? - Test for raw wtforms form
- Localization
- Create documentation
- Model Admin
- Simplify scaffold_list_columns - remove excluded_list_columns check, update documentation
- Simplify InlineModelFormList implementation - accept property instead of property name
- Reduce number of parameters passed to list view
- Filters
- Use table to draw filters so column names will line up?
- Change boolean filter to True/False instead of Yes/No
- Ability to sort by fields that are not visible?
- List display callables?
- MongoEngine
- EmbeddedDocument customization
- File admin
- Header title
- File size restriction
- Unit tests
- Form generation tests
- Documentation
- Add all new stuff
- Fixed stylesheet
...@@ -12,7 +12,7 @@ Changelog ...@@ -12,7 +12,7 @@ Changelog
* Bootstrap 2.3.1 * Bootstrap 2.3.1
* Bulk deletes go through `delete_model` * Bulk deletes go through `delete_model`
* Flask-Admin no longer uses floating navigation bar * Flask-Admin no longer uses floating navigation bar
* Translations: French, Persian (Farsi), Chinese (Simplified/Traditional), Chech * Translations: French, Persian (Farsi), Chinese (Simplified/Traditional), Czech
* Bug fixes * Bug fixes
1.0.5 1.0.5
......
...@@ -15,9 +15,9 @@ with PyMongo: ...@@ -15,9 +15,9 @@ with PyMongo:
This is minimal PyMongo view:: This is minimal PyMongo view::
class UserForm(wtf.Form): class UserForm(Form):
name = wtf.TextForm('Name') name = TextField('Name')
email = wtf.TextForm('Email') email = TextField('Email')
class UserView(ModelView): class UserView(ModelView):
column_list = ('name', 'email') column_list = ('name', 'email')
......
...@@ -97,7 +97,7 @@ you can do something like this:: ...@@ -97,7 +97,7 @@ you can do something like this::
class UserView(ModelView): class UserView(ModelView):
def scaffold_form(self): def scaffold_form(self):
form_class = super(UserView, self).scaffold_form() form_class = super(UserView, self).scaffold_form()
form_class.extra = wtf.TextField('Extra') form_class.extra = TextField('Extra')
return form_class return form_class
Check :doc:`api/mod_contrib_sqlamodel` documentation for list of Check :doc:`api/mod_contrib_sqlamodel` documentation for list of
......
...@@ -163,7 +163,7 @@ Steps to add new model backend: ...@@ -163,7 +163,7 @@ Steps to add new model backend:
class MyDbModel(BaseModelView): class MyDbModel(BaseModelView):
def scaffold_form(self): def scaffold_form(self):
class MyForm(wtf.Form): class MyForm(Form):
pass pass
# Do something # Do something
......
...@@ -24,5 +24,5 @@ field. In this case, you need to manually contribute field:: ...@@ -24,5 +24,5 @@ field. In this case, you need to manually contribute field::
class MyView(ModelView): class MyView(ModelView):
def scaffold_form(self): def scaffold_form(self):
form_class = super(UserView, self).scaffold_form() form_class = super(UserView, self).scaffold_form()
form_class.extra = wtf.TextField('Extra') form_class.extra = TextField('Extra')
return form_class return form_class
from flask import Flask, url_for, redirect, render_template, request from flask import Flask, url_for, redirect, render_template, request
from flask.ext.mongoengine import MongoEngine from flask.ext.mongoengine import MongoEngine
from flask.ext import admin, login, wtf from wtforms import form, fields, validators
from flask.ext import admin, login
from flask.ext.admin.contrib.mongoengine import ModelView from flask.ext.admin.contrib.mongoengine import ModelView
from flask.ext.admin import helpers
# Create application # Create application
app = Flask(__name__) app = Flask(__name__)
...@@ -42,31 +45,31 @@ class User(db.Document): ...@@ -42,31 +45,31 @@ class User(db.Document):
# Define login and registration forms (for flask-login) # Define login and registration forms (for flask-login)
class LoginForm(wtf.Form): class LoginForm(form.Form):
login = wtf.TextField(validators=[wtf.required()]) login = fields.TextField(validators=[validators.required()])
password = wtf.PasswordField(validators=[wtf.required()]) password = fields.PasswordField(validators=[validators.required()])
def validate_login(self, field): def validate_login(self, field):
user = self.get_user() user = self.get_user()
if user is None: if user is None:
raise wtf.ValidationError('Invalid user') raise validators.ValidationError('Invalid user')
if user.password != self.password.data: if user.password != self.password.data:
raise wtf.ValidationError('Invalid password') raise validators.ValidationError('Invalid password')
def get_user(self): def get_user(self):
return User.objects(login=self.login.data).first() return User.objects(login=self.login.data).first()
class RegistrationForm(wtf.Form): class RegistrationForm(form.Form):
login = wtf.TextField(validators=[wtf.required()]) login = fields.TextField(validators=[validators.required()])
email = wtf.TextField() email = fields.TextField()
password = wtf.PasswordField(validators=[wtf.required()]) password = fields.PasswordField(validators=[validators.required()])
def validate_login(self, field): def validate_login(self, field):
if User.objects(login=self.login.data): if User.objects(login=self.login.data):
raise wtf.ValidationError('Duplicate username') raise validators.ValidationError('Duplicate username')
# Initialize flask-login # Initialize flask-login
...@@ -101,7 +104,7 @@ def index(): ...@@ -101,7 +104,7 @@ def index():
@app.route('/login/', methods=('GET', 'POST')) @app.route('/login/', methods=('GET', 'POST'))
def login_view(): def login_view():
form = LoginForm(request.form) form = LoginForm(request.form)
if form.validate_on_submit(): if helpers.validate_form_on_submit(form):
user = form.get_user() user = form.get_user()
login.login_user(user) login.login_user(user)
return redirect(url_for('index')) return redirect(url_for('index'))
...@@ -112,7 +115,7 @@ def login_view(): ...@@ -112,7 +115,7 @@ def login_view():
@app.route('/register/', methods=('GET', 'POST')) @app.route('/register/', methods=('GET', 'POST'))
def register_view(): def register_view():
form = RegistrationForm(request.form) form = RegistrationForm(request.form)
if form.validate_on_submit(): if helpers.validate_form_on_submit(form):
user = User() user = User()
form.populate_obj(user) form.populate_obj(user)
...@@ -140,5 +143,4 @@ if __name__ == '__main__': ...@@ -140,5 +143,4 @@ if __name__ == '__main__':
admin.add_view(MyModelView(User)) admin.add_view(MyModelView(User))
# Start app # Start app
app.debug = True app.run(debug=True)
app.run()
from flask import Flask, url_for, redirect, render_template, request from flask import Flask, url_for, redirect, render_template, request
from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext import admin, login, wtf from wtforms import form, fields, validators
from flask.ext import admin, login
from flask.ext.admin.contrib import sqlamodel from flask.ext.admin.contrib import sqlamodel
from flask.ext.admin import helpers
# Create Flask application # Create Flask application
app = Flask(__name__) app = Flask(__name__)
...@@ -43,31 +46,31 @@ class User(db.Model): ...@@ -43,31 +46,31 @@ class User(db.Model):
# Define login and registration forms (for flask-login) # Define login and registration forms (for flask-login)
class LoginForm(wtf.Form): class LoginForm(form.Form):
login = wtf.TextField(validators=[wtf.required()]) login = fields.TextField(validators=[validators.required()])
password = wtf.PasswordField(validators=[wtf.required()]) password = fields.PasswordField(validators=[validators.required()])
def validate_login(self, field): def validate_login(self, field):
user = self.get_user() user = self.get_user()
if user is None: if user is None:
raise wtf.ValidationError('Invalid user') raise validators.ValidationError('Invalid user')
if user.password != self.password.data: if user.password != self.password.data:
raise wtf.ValidationError('Invalid password') raise validators.ValidationError('Invalid password')
def get_user(self): def get_user(self):
return db.session.query(User).filter_by(login=self.login.data).first() return db.session.query(User).filter_by(login=self.login.data).first()
class RegistrationForm(wtf.Form): class RegistrationForm(form.Form):
login = wtf.TextField(validators=[wtf.required()]) login = fields.TextField(validators=[validators.required()])
email = wtf.TextField() email = fields.TextField()
password = wtf.PasswordField(validators=[wtf.required()]) password = fields.PasswordField(validators=[validators.required()])
def validate_login(self, field): def validate_login(self, field):
if db.session.query(User).filter_by(login=self.login.data).count() > 0: if db.session.query(User).filter_by(login=self.login.data).count() > 0:
raise wtf.ValidationError('Duplicate username') raise validators.ValidationError('Duplicate username')
# Initialize flask-login # Initialize flask-login
...@@ -102,7 +105,7 @@ def index(): ...@@ -102,7 +105,7 @@ def index():
@app.route('/login/', methods=('GET', 'POST')) @app.route('/login/', methods=('GET', 'POST'))
def login_view(): def login_view():
form = LoginForm(request.form) form = LoginForm(request.form)
if form.validate_on_submit(): if helpers.validate_form_on_submit(form):
user = form.get_user() user = form.get_user()
login.login_user(user) login.login_user(user)
return redirect(url_for('index')) return redirect(url_for('index'))
...@@ -113,7 +116,7 @@ def login_view(): ...@@ -113,7 +116,7 @@ def login_view():
@app.route('/register/', methods=('GET', 'POST')) @app.route('/register/', methods=('GET', 'POST'))
def register_view(): def register_view():
form = RegistrationForm(request.form) form = RegistrationForm(request.form)
if form.validate_on_submit(): if helpers.validate_form_on_submit(form):
user = User() user = User()
form.populate_obj(user) form.populate_obj(user)
...@@ -146,5 +149,4 @@ if __name__ == '__main__': ...@@ -146,5 +149,4 @@ if __name__ == '__main__':
db.create_all() db.create_all()
# Start app # Start app
app.debug = True app.run(debug=True)
app.run()
...@@ -74,5 +74,4 @@ if __name__ == '__main__': ...@@ -74,5 +74,4 @@ if __name__ == '__main__':
db.create_all() db.create_all()
# Start app # Start app
app.debug = True app.run(debug=True)
app.run('0.0.0.0', 8000)
...@@ -33,5 +33,4 @@ if __name__ == '__main__': ...@@ -33,5 +33,4 @@ if __name__ == '__main__':
admin.add_view(fileadmin.FileAdmin(path, '/files/', name='Files')) admin.add_view(fileadmin.FileAdmin(path, '/files/', name='Files'))
# Start app # Start app
app.debug = True app.run(debug=True)
app.run()
...@@ -65,5 +65,4 @@ if __name__ == '__main__': ...@@ -65,5 +65,4 @@ if __name__ == '__main__':
db.create_all() db.create_all()
# Start app # Start app
app.debug = True app.run(debug=True)
app.run('0.0.0.0', 8000)
...@@ -43,5 +43,4 @@ if __name__ == '__main__': ...@@ -43,5 +43,4 @@ if __name__ == '__main__':
admin.init_app(app) admin.init_app(app)
# Start app # Start app
app.debug = True app.run(debug=True)
app.run()
...@@ -87,5 +87,4 @@ if __name__ == '__main__': ...@@ -87,5 +87,4 @@ if __name__ == '__main__':
admin.add_view(ModelView(Post)) admin.add_view(ModelView(Post))
# Start app # Start app
app.debug = True app.run(debug=True)
app.run('0.0.0.0', 8000)
...@@ -36,5 +36,4 @@ if __name__ == '__main__': ...@@ -36,5 +36,4 @@ if __name__ == '__main__':
admin2.add_view(SecondView()) admin2.add_view(SecondView())
# Start app # Start app
app.debug = True app.run(debug=True)
app.run()
...@@ -89,5 +89,4 @@ if __name__ == '__main__': ...@@ -89,5 +89,4 @@ if __name__ == '__main__':
except: except:
pass pass
app.debug = True app.run(debug=True)
app.run('0.0.0.0', 8000)
...@@ -2,12 +2,13 @@ import pymongo ...@@ -2,12 +2,13 @@ import pymongo
from bson.objectid import ObjectId from bson.objectid import ObjectId
from flask import Flask from flask import Flask
from flask.ext import admin
from flask.ext import admin, wtf from wtforms import form, fields
from flask.ext.admin.form import Select2Widget from flask.ext.admin.form import Select2Widget
from flask.ext.admin.contrib.pymongo import ModelView, filters from flask.ext.admin.contrib.pymongo import ModelView, filters
from flask.ext.admin.model import fields from flask.ext.admin.model.fields import InlineFormField, InlineFieldList
# Create application # Create application
app = Flask(__name__) app = Flask(__name__)
...@@ -21,21 +22,21 @@ db = conn.test ...@@ -21,21 +22,21 @@ db = conn.test
# User admin # User admin
class InnerForm(wtf.Form): class InnerForm(form.Form):
name = wtf.TextField('Name') name = fields.TextField('Name')
test = wtf.TextField('Test') test = fields.TextField('Test')
class UserForm(wtf.Form): class UserForm(form.Form):
name = wtf.TextField('Name') name = fields.TextField('Name')
email = wtf.TextField('Email') email = fields.TextField('Email')
password = wtf.TextField('Password') password = fields.TextField('Password')
# Inner form # Inner form
inner = fields.InlineFormField(InnerForm) inner = InlineFormField(InnerForm)
# Form list # Form list
form_list = fields.InlineFieldList(fields.InlineFormField(InnerForm)) form_list = InlineFieldList(InlineFormField(InnerForm))
class UserView(ModelView): class UserView(ModelView):
...@@ -46,10 +47,10 @@ class UserView(ModelView): ...@@ -46,10 +47,10 @@ class UserView(ModelView):
# Tweet view # Tweet view
class TweetForm(wtf.Form): class TweetForm(form.Form):
name = wtf.TextField('Name') name = fields.TextField('Name')
user_id = wtf.SelectField('User', widget=Select2Widget()) user_id = fields.SelectField('User', widget=Select2Widget())
text = wtf.TextField('Text') text = fields.TextField('Text')
class TweetView(ModelView): class TweetView(ModelView):
...@@ -97,9 +98,7 @@ class TweetView(ModelView): ...@@ -97,9 +98,7 @@ class TweetView(ModelView):
# Correct user_id reference before saving # Correct user_id reference before saving
def on_model_change(self, form, model): def on_model_change(self, form, model):
user_id = model.get('user_id') user_id = model.get('user_id')
model['user_id'] = ObjectId(user_id)
if isinstance(user_id, basestring):
model['user_id'] = ObjectId(user_id)
return model return model
...@@ -119,5 +118,4 @@ if __name__ == '__main__': ...@@ -119,5 +118,4 @@ if __name__ == '__main__':
admin.add_view(TweetView(db.tweet, 'Tweets')) admin.add_view(TweetView(db.tweet, 'Tweets'))
# Start app # Start app
app.debug = True app.run(debug=True)
app.run('0.0.0.0', 8000)
...@@ -5,4 +5,4 @@ from flask.ext.admin import Admin ...@@ -5,4 +5,4 @@ from flask.ext.admin import Admin
app = Flask(__name__) app = Flask(__name__)
admin = Admin(app) admin = Admin(app)
app.run() app.run(debug=True)
...@@ -12,4 +12,4 @@ app = Flask(__name__) ...@@ -12,4 +12,4 @@ app = Flask(__name__)
admin = Admin(app) admin = Admin(app)
admin.add_view(MyView(name='Hello')) admin.add_view(MyView(name='Hello'))
app.run() app.run(debug=True)
...@@ -12,4 +12,4 @@ admin = Admin(app) ...@@ -12,4 +12,4 @@ admin = Admin(app)
admin.add_view(MyView(name='Hello 1', endpoint='test1', category='Test')) admin.add_view(MyView(name='Hello 1', endpoint='test1', category='Test'))
admin.add_view(MyView(name='Hello 2', endpoint='test2', category='Test')) admin.add_view(MyView(name='Hello 2', endpoint='test2', category='Test'))
admin.add_view(MyView(name='Hello 3', endpoint='test3', category='Test')) admin.add_view(MyView(name='Hello 3', endpoint='test3', category='Test'))
app.run() app.run(debug=True)
...@@ -38,5 +38,4 @@ if __name__ == '__main__': ...@@ -38,5 +38,4 @@ if __name__ == '__main__':
admin.init_app(app) admin.init_app(app)
# Start app # Start app
app.debug = True app.run(debug=True)
app.run()
from flask import Flask from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext import admin, wtf from wtforms import validators
from flask.ext import admin
from flask.ext.admin.contrib import sqlamodel from flask.ext.admin.contrib import sqlamodel
from flask.ext.admin.contrib.sqlamodel import filters from flask.ext.admin.contrib.sqlamodel import filters
...@@ -24,7 +26,7 @@ class User(db.Model): ...@@ -24,7 +26,7 @@ class User(db.Model):
email = db.Column(db.String(120), unique=True) email = db.Column(db.String(120), unique=True)
# Required for administrative interface # Required for administrative interface
def __unicode__(self): def __str__(self):
return self.username return self.username
...@@ -46,7 +48,7 @@ class Post(db.Model): ...@@ -46,7 +48,7 @@ class Post(db.Model):
tags = db.relationship('Tag', secondary=post_tags_table) tags = db.relationship('Tag', secondary=post_tags_table)
def __unicode__(self): def __str__(self):
return self.title return self.title
...@@ -54,7 +56,7 @@ class Tag(db.Model): ...@@ -54,7 +56,7 @@ class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Unicode(64)) name = db.Column(db.Unicode(64))
def __unicode__(self): def __str__(self):
return self.name return self.name
...@@ -67,7 +69,7 @@ class UserInfo(db.Model): ...@@ -67,7 +69,7 @@ class UserInfo(db.Model):
user_id = db.Column(db.Integer(), db.ForeignKey(User.id)) user_id = db.Column(db.Integer(), db.ForeignKey(User.id))
user = db.relationship(User, backref='info') user = db.relationship(User, backref='info')
def __unicode__(self): def __str__(self):
return '%s - %s' % (self.key, self.value) return '%s - %s' % (self.key, self.value)
...@@ -77,7 +79,7 @@ class Tree(db.Model): ...@@ -77,7 +79,7 @@ class Tree(db.Model):
parent_id = db.Column(db.Integer, db.ForeignKey('tree.id')) parent_id = db.Column(db.Integer, db.ForeignKey('tree.id'))
parent = db.relationship('Tree', remote_side=[id], backref='children') parent = db.relationship('Tree', remote_side=[id], backref='children')
def __unicode__(self): def __str__(self):
return self.name return self.name
...@@ -114,7 +116,7 @@ class PostAdmin(sqlamodel.ModelView): ...@@ -114,7 +116,7 @@ class PostAdmin(sqlamodel.ModelView):
# Pass arguments to WTForms. In this case, change label for text field to # Pass arguments to WTForms. In this case, change label for text field to
# be 'Big Text' and add required() validator. # be 'Big Text' and add required() validator.
form_args = dict( form_args = dict(
text=dict(label='Big Text', validators=[wtf.required()]) text=dict(label='Big Text', validators=[validators.required()])
) )
def __init__(self, session): def __init__(self, session):
...@@ -140,5 +142,4 @@ if __name__ == '__main__': ...@@ -140,5 +142,4 @@ if __name__ == '__main__':
db.create_all() db.create_all()
# Start app # Start app
app.debug = True app.run(debug=True)
app.run('0.0.0.0', 8000)
from flask import Flask from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext import admin, wtf from wtforms import fields, widgets
from flask.ext import admin
from flask.ext.admin.contrib import sqlamodel from flask.ext.admin.contrib import sqlamodel
# Create application # Create application
...@@ -17,13 +19,13 @@ db = SQLAlchemy(app) ...@@ -17,13 +19,13 @@ db = SQLAlchemy(app)
# Define wtforms widget and field # Define wtforms widget and field
class CKTextAreaWidget(wtf.TextArea): class CKTextAreaWidget(widgets.TextArea):
def __call__(self, field, **kwargs): def __call__(self, field, **kwargs):
kwargs.setdefault('class_', 'ckeditor') kwargs.setdefault('class_', 'ckeditor')
return super(CKTextAreaWidget, self).__call__(field, **kwargs) return super(CKTextAreaWidget, self).__call__(field, **kwargs)
class CKTextAreaField(wtf.TextAreaField): class CKTextAreaField(fields.TextAreaField):
widget = CKTextAreaWidget() widget = CKTextAreaWidget()
...@@ -62,5 +64,4 @@ if __name__ == '__main__': ...@@ -62,5 +64,4 @@ if __name__ == '__main__':
db.create_all() db.create_all()
# Start app # Start app
app.debug = True app.run(debug=True)
app.run('0.0.0.0', 8000)
# -*- coding: utf-8 -*-
"""
flask.ext.admin._compat
~~~~~~~~~~~~~~~~~~~~~~~
Some py2/py3 compatibility support based on a stripped down
version of six so we don't have to depend on a specific version
of it.
:copyright: (c) 2013 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import sys
PY2 = sys.version_info[0] == 2
if not PY2:
text_type = str
string_types = (str,)
integer_types = (int, )
iterkeys = lambda d: iter(d.keys())
itervalues = lambda d: iter(d.values())
iteritems = lambda d: iter(d.items())
def as_unicode(s):
if isinstance(s, bytes):
return s.decode('utf-8')
return str(s)
# Various tools
from functools import reduce
from urllib.parse import urljoin
else:
text_type = unicode
string_types = (str, unicode)
integer_types = (int, long)
iterkeys = lambda d: d.iterkeys()
itervalues = lambda d: d.itervalues()
iteritems = lambda d: d.iteritems()
def as_unicode(s):
if isinstance(s, str):
return s.decode('utf-8')
return unicode(s)
# Helpers
reduce = __builtins__['reduce']
from urlparse import urljoin
def with_metaclass(meta, *bases):
# This requires a bit of explanation: the basic idea is to make a
# dummy metaclass for one level of class instantiation that replaces
# itself with the actual metaclass. Because of internal type checks
# we also need to make sure that we downgrade the custom metaclass
# for one level to something closer to type (that's why __call__ and
# __init__ comes back from type etc.).
#
# This has the advantage over six.with_metaclass in that it does not
# introduce dummy classes into the final MRO.
class metaclass(meta):
__call__ = type.__call__
__init__ = type.__init__
def __new__(cls, name, this_bases, d):
if this_bases is None:
return type.__new__(cls, name, (), d)
return meta(name, bases, d)
return metaclass('temporary_class', None, {})
from flask import request, url_for, redirect from flask import request, url_for, redirect
from flask.ext.admin.tools import get_dict_attr from flask.ext.admin import tools
from flask.ext.admin._compat import text_type
def action(name, text, confirmation=None): def action(name, text, confirmation=None):
...@@ -53,7 +54,7 @@ class ActionsMixin(object): ...@@ -53,7 +54,7 @@ class ActionsMixin(object):
self._actions_data = {} self._actions_data = {}
for p in dir(self): for p in dir(self):
attr = get_dict_attr(self, p) attr = tools.get_dict_attr(self, p)
if hasattr(attr, '_action'): if hasattr(attr, '_action'):
name, text, desc = attr._action name, text, desc = attr._action
...@@ -85,12 +86,11 @@ class ActionsMixin(object): ...@@ -85,12 +86,11 @@ class ActionsMixin(object):
name, text = act name, text = act
if self.is_action_allowed(name): if self.is_action_allowed(name):
text = unicode(text) actions.append((name, text_type(text)))
actions.append((name, text))
confirmation = self._actions_data[name][2] confirmation = self._actions_data[name][2]
if confirmation: if confirmation:
actions_confirmation[name] = unicode(confirmation) actions_confirmation[name] = text_type(confirmation)
return actions, actions_confirmation return actions, actions_confirmation
......
...@@ -3,6 +3,7 @@ from re import sub ...@@ -3,6 +3,7 @@ from re import sub
from flask import Blueprint, render_template, url_for, abort, g from flask import Blueprint, render_template, url_for, abort, g
from flask.ext.admin import babel from flask.ext.admin import babel
from flask.ext.admin._compat import with_metaclass
from flask.ext.admin import helpers as h from flask.ext.admin import helpers as h
...@@ -90,7 +91,11 @@ class AdminViewMeta(type): ...@@ -90,7 +91,11 @@ class AdminViewMeta(type):
setattr(cls, p, _wrap_view(attr)) setattr(cls, p, _wrap_view(attr))
class BaseView(object): class BaseViewClass(object):
pass
class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)):
""" """
Base administrative view. Base administrative view.
...@@ -101,8 +106,6 @@ class BaseView(object): ...@@ -101,8 +106,6 @@ class BaseView(object):
def index(self): def index(self):
return 'Hello World!' return 'Hello World!'
""" """
__metaclass__ = AdminViewMeta
@property @property
def _template_args(self): def _template_args(self):
""" """
...@@ -168,7 +171,7 @@ class BaseView(object): ...@@ -168,7 +171,7 @@ class BaseView(object):
# Default view # Default view
if self._default_view is None: if self._default_view is None:
raise Exception('Attempted to instantiate admin view %s without default view' % self.__class__.__name__) raise Exception(u'Attempted to instantiate admin view %s without default view' % self.__class__.__name__)
def create_blueprint(self, admin): def create_blueprint(self, admin):
""" """
...@@ -466,7 +469,7 @@ class Admin(object): ...@@ -466,7 +469,7 @@ class Admin(object):
self.locale_selector_func = None self.locale_selector_func = None
# Register with application # Register with application
if app: if app is not None:
self._init_extension() self._init_extension()
def add_view(self, view): def add_view(self, view):
...@@ -520,7 +523,7 @@ class Admin(object): ...@@ -520,7 +523,7 @@ class Admin(object):
return request.args.get('lang', 'en') return request.args.get('lang', 'en')
""" """
if self.locale_selector_func is not None: if self.locale_selector_func is not None:
raise Exception('Can not add locale_selector second time.') raise Exception(u'Can not add locale_selector second time.')
self.locale_selector_func = f self.locale_selector_func = f
...@@ -567,12 +570,12 @@ class Admin(object): ...@@ -567,12 +570,12 @@ class Admin(object):
for p in admins: for p in admins:
if p.endpoint == self.endpoint: if p.endpoint == self.endpoint:
raise Exception('Cannot have two Admin() instances with same' raise Exception(u'Cannot have two Admin() instances with same'
' endpoint name.') u' endpoint name.')
if p.url == self.url and p.subdomain == self.subdomain: if p.url == self.url and p.subdomain == self.subdomain:
raise Exception('Cannot assign two Admin() instances with same' raise Exception(u'Cannot assign two Admin() instances with same'
' URL and subdomain to the same application.') u' URL and subdomain to the same application.')
admins.append(self) admins.append(self)
self.app.extensions['admin'] = admins self.app.extensions['admin'] = admins
......
import os import os
import os.path as op import os.path as op
import platform import platform
import urlparse
import re import re
import shutil import shutil
from operator import itemgetter from operator import itemgetter
from werkzeug import secure_filename from werkzeug import secure_filename
from flask import flash, url_for, redirect, abort, request from flask import flash, url_for, redirect, abort, request
from wtforms import fields, validators
from flask.ext.admin import form, helpers
from flask.ext.admin._compat import urljoin
from flask.ext.admin.base import BaseView, expose from flask.ext.admin.base import BaseView, expose
from flask.ext.admin.actions import action, ActionsMixin 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
from flask.ext.admin import form
from flask.ext import wtf
class NameForm(form.BaseForm): class NameForm(form.BaseForm):
...@@ -24,13 +24,13 @@ class NameForm(form.BaseForm): ...@@ -24,13 +24,13 @@ class NameForm(form.BaseForm):
Validates if provided name is valid for *nix and Windows systems. Validates if provided name is valid for *nix and Windows systems.
""" """
name = wtf.TextField() name = fields.TextField()
regexp = re.compile(r'^(?!^(PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d|\..*)(\..+)?$)[^\x00-\x1f\\?*:\";|/]+$') regexp = re.compile(r'^(?!^(PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d|\..*)(\..+)?$)[^\x00-\x1f\\?*:\";|/]+$')
def validate_name(self, field): def validate_name(self, field):
if not self.regexp.match(field.data): if not self.regexp.match(field.data):
raise wtf.ValidationError(gettext('Invalid directory name')) raise validators.ValidationError(gettext('Invalid directory name'))
class UploadForm(form.BaseForm): class UploadForm(form.BaseForm):
...@@ -38,26 +38,26 @@ class UploadForm(form.BaseForm): ...@@ -38,26 +38,26 @@ class UploadForm(form.BaseForm):
File upload form. Works with FileAdmin instance to check if it is allowed File upload form. Works with FileAdmin instance to check if it is allowed
to upload file with given extension. to upload file with given extension.
""" """
upload = wtf.FileField(lazy_gettext('File to upload')) upload = fields.FileField(lazy_gettext('File to upload'))
def __init__(self, admin): def __init__(self, admin):
self.admin = admin self.admin = admin
super(UploadForm, self).__init__() super(UploadForm, self).__init__(helpers.get_form_data())
def validate_upload(self, field): def validate_upload(self, field):
if not self.upload.has_file(): if not self.upload.has_file():
raise wtf.ValidationError(gettext('File required.')) raise validators.ValidationError(gettext('File required.'))
filename = self.upload.data.filename filename = self.upload.data.filename
if not self.admin.is_file_allowed(filename): if not self.admin.is_file_allowed(filename):
raise wtf.ValidationError(gettext('Invalid file type.')) raise validators.ValidationError(gettext('Invalid file type.'))
class EditForm(form.BaseForm): class EditForm(form.BaseForm):
content = wtf.TextAreaField(lazy_gettext('Content'), content = fields.TextAreaField(lazy_gettext('Content'),
[wtf.validators.required()]) (validators.required(),))
class FileAdmin(BaseView, ActionsMixin): class FileAdmin(BaseView, ActionsMixin):
...@@ -181,13 +181,13 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -181,13 +181,13 @@ class FileAdmin(BaseView, ActionsMixin):
self._on_windows = platform.system() == 'Windows' self._on_windows = platform.system() == 'Windows'
# Convert allowed_extensions to set for quick validation # Convert allowed_extensions to set for quick validation
if (self.allowed_extensions if (self.allowed_extensions and
and not isinstance(self.allowed_extensions, set)): not isinstance(self.allowed_extensions, set)):
self.allowed_extensions = set(self.allowed_extensions) self.allowed_extensions = set(self.allowed_extensions)
# Convert editable_extensions to set for quick validation # Convert editable_extensions to set for quick validation
if (self.editable_extensions if (self.editable_extensions and
and not isinstance(self.editable_extensions, set)): not isinstance(self.editable_extensions, set)):
self.editable_extensions = set(self.editable_extensions) self.editable_extensions = set(self.editable_extensions)
# Check if path exists # Check if path exists
...@@ -313,7 +313,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -313,7 +313,7 @@ class FileAdmin(BaseView, ActionsMixin):
return url_for(".edit", path=path) return url_for(".edit", path=path)
else: else:
base_url = self.get_base_url() base_url = self.get_base_url()
return urlparse.urljoin(base_url, path) return urljoin(base_url, path)
def _normalize_path(self, path): def _normalize_path(self, path):
""" """
...@@ -476,7 +476,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -476,7 +476,7 @@ class FileAdmin(BaseView, ActionsMixin):
return redirect(self._get_dir_url('.index', path)) return redirect(self._get_dir_url('.index', path))
form = UploadForm(self) form = UploadForm(self)
if form.validate_on_submit(): if helpers.validate_form_on_submit(form):
filename = op.join(directory, filename = op.join(directory,
secure_filename(form.upload.data.filename)) secure_filename(form.upload.data.filename))
...@@ -488,7 +488,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -488,7 +488,7 @@ class FileAdmin(BaseView, ActionsMixin):
self.save_file(filename, form.upload.data) self.save_file(filename, form.upload.data)
self.on_file_upload(directory, path, filename) self.on_file_upload(directory, path, filename)
return redirect(self._get_dir_url('.index', path)) return redirect(self._get_dir_url('.index', path))
except Exception, 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))
return self.render(self.upload_template, form=form) return self.render(self.upload_template, form=form)
...@@ -511,14 +511,14 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -511,14 +511,14 @@ class FileAdmin(BaseView, ActionsMixin):
flash(gettext('Directory creation is disabled.'), 'error') flash(gettext('Directory creation is disabled.'), 'error')
return redirect(dir_url) return redirect(dir_url)
form = NameForm(request.form) form = NameForm(helpers.get_form_data())
if form.validate_on_submit(): if helpers.validate_form_on_submit(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, ex: except Exception as ex:
flash(gettext('Failed to create directory: %(error)s', ex), 'error') flash(gettext('Failed to create directory: %(error)s', ex), 'error')
return self.render(self.mkdir_template, return self.render(self.mkdir_template,
...@@ -553,14 +553,14 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -553,14 +553,14 @@ class FileAdmin(BaseView, ActionsMixin):
shutil.rmtree(full_path) shutil.rmtree(full_path)
self.on_directory_delete(full_path, path) self.on_directory_delete(full_path, path)
flash(gettext('Directory "%s" was successfully deleted.' % path)) flash(gettext('Directory "%s" was successfully deleted.' % path))
except Exception, ex: except Exception as ex:
flash(gettext('Failed to delete directory: %(error)s', error=ex), 'error') flash(gettext('Failed to delete directory: %(error)s', error=ex), 'error')
else: else:
try: try:
os.remove(full_path) os.remove(full_path)
self.on_file_delete(full_path, path) self.on_file_delete(full_path, path)
flash(gettext('File "%(name)s" was successfully deleted.', name=path)) flash(gettext('File "%(name)s" was successfully deleted.', name=path))
except Exception, 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')
return redirect(return_url) return redirect(return_url)
...@@ -587,8 +587,8 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -587,8 +587,8 @@ class FileAdmin(BaseView, ActionsMixin):
flash(gettext('Path does not exist.')) flash(gettext('Path does not exist.'))
return redirect(return_url) return redirect(return_url)
form = NameForm(request.form, name=op.basename(path)) form = NameForm(helpers.get_form_data(), name=op.basename(path))
if form.validate_on_submit(): 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)
...@@ -598,7 +598,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -598,7 +598,7 @@ class FileAdmin(BaseView, ActionsMixin):
flash(gettext('Successfully renamed "%(src)s" to "%(dst)s"', flash(gettext('Successfully renamed "%(src)s" to "%(dst)s"',
src=op.basename(path), src=op.basename(path),
dst=filename)) dst=filename))
except Exception, ex: except Exception as ex:
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)
...@@ -627,10 +627,10 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -627,10 +627,10 @@ class FileAdmin(BaseView, ActionsMixin):
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() form = EditForm(helpers.get_form_data())
error = False error = False
if request.method == 'POST': if helpers.validate_form_on_submit(form):
form.process(request.form, content='') form.process(request.form, content='')
if form.validate(): if form.validate():
try: try:
...@@ -683,7 +683,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -683,7 +683,7 @@ class FileAdmin(BaseView, ActionsMixin):
try: try:
os.remove(full_path) os.remove(full_path)
flash(gettext('File "%(name)s" was successfully deleted.', name=path)) flash(gettext('File "%(name)s" was successfully deleted.', name=path))
except Exception, 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')
@action('edit', lazy_gettext('Edit')) @action('edit', lazy_gettext('Edit'))
......
...@@ -4,6 +4,7 @@ from flask import flash ...@@ -4,6 +4,7 @@ from flask import flash
from flask.ext.admin.babel import gettext, ngettext, lazy_gettext from flask.ext.admin.babel import gettext, ngettext, lazy_gettext
from flask.ext.admin.model import BaseModelView from flask.ext.admin.model import BaseModelView
from flask.ext.admin._compat import iteritems, string_types
import mongoengine import mongoengine
from bson.objectid import ObjectId from bson.objectid import ObjectId
...@@ -115,7 +116,7 @@ class ModelView(BaseModelView): ...@@ -115,7 +116,7 @@ class ModelView(BaseModelView):
if model is None: if model is None:
model = self.model model = self.model
return sorted(model._fields.iteritems(), key=lambda n: n[1].creation_counter) return sorted(iteritems(model._fields), key=lambda n: n[1].creation_counter)
def scaffold_pk(self): def scaffold_pk(self):
# MongoEngine models have predefined 'id' as a key # MongoEngine models have predefined 'id' as a key
...@@ -171,7 +172,7 @@ class ModelView(BaseModelView): ...@@ -171,7 +172,7 @@ class ModelView(BaseModelView):
""" """
if self.column_searchable_list: if self.column_searchable_list:
for p in self.column_searchable_list: for p in self.column_searchable_list:
if isinstance(p, basestring): if isinstance(p, string_types):
p = self.model._fields.get(p) p = self.model._fields.get(p)
if p is None: if p is None:
...@@ -195,7 +196,7 @@ class ModelView(BaseModelView): ...@@ -195,7 +196,7 @@ class ModelView(BaseModelView):
:param name: :param name:
Either field name or field instance Either field name or field instance
""" """
if isinstance(name, basestring): if isinstance(name, string_types):
attr = self.model._fields.get(name) attr = self.model._fields.get(name)
else: else:
attr = name attr = name
...@@ -206,7 +207,7 @@ class ModelView(BaseModelView): ...@@ -206,7 +207,7 @@ class ModelView(BaseModelView):
# Find name # Find name
visible_name = None visible_name = None
if not isinstance(name, basestring): if not isinstance(name, string_types):
visible_name = self.get_column_name(attr.name) visible_name = self.get_column_name(attr.name)
if not visible_name: if not visible_name:
...@@ -232,11 +233,11 @@ class ModelView(BaseModelView): ...@@ -232,11 +233,11 @@ class ModelView(BaseModelView):
def scaffold_form(self): def scaffold_form(self):
# TODO: Fix base_class # TODO: Fix base_class
form_class = model_form(self.model, form_class = model_form(self.model,
base_class=BaseForm, base_class=BaseForm,
only=self.form_columns, only=self.form_columns,
exclude=self.form_excluded_columns, exclude=self.form_excluded_columns,
field_args=self.form_args, field_args=self.form_args,
converter=self.model_form_converter()) converter=self.model_form_converter())
return form_class return form_class
...@@ -338,7 +339,7 @@ class ModelView(BaseModelView): ...@@ -338,7 +339,7 @@ class ModelView(BaseModelView):
form.populate_obj(model) form.populate_obj(model)
self.on_model_change(form, model) self.on_model_change(form, model)
model.save() model.save()
except Exception, ex: except Exception as ex:
flash(gettext('Failed to create model. %(error)s', error=str(ex)), flash(gettext('Failed to create model. %(error)s', error=str(ex)),
'error') 'error')
logging.exception('Failed to create model') logging.exception('Failed to create model')
...@@ -361,7 +362,7 @@ class ModelView(BaseModelView): ...@@ -361,7 +362,7 @@ class ModelView(BaseModelView):
form.populate_obj(model) form.populate_obj(model)
self.on_model_change(form, model) self.on_model_change(form, model)
model.save() model.save()
except Exception, ex: except Exception as ex:
flash(gettext('Failed to update model. %(error)s', error=str(ex)), flash(gettext('Failed to update model. %(error)s', error=str(ex)),
'error') 'error')
logging.exception('Failed to update model') logging.exception('Failed to update model')
...@@ -382,7 +383,7 @@ class ModelView(BaseModelView): ...@@ -382,7 +383,7 @@ class ModelView(BaseModelView):
self.on_model_delete(model) self.on_model_delete(model)
model.delete() model.delete()
return True return True
except Exception, ex: except Exception as ex:
flash(gettext('Failed to delete model. %(error)s', error=str(ex)), flash(gettext('Failed to delete model. %(error)s', error=str(ex)),
'error') 'error')
logging.exception('Failed to delete model') logging.exception('Failed to delete model')
...@@ -411,6 +412,6 @@ class ModelView(BaseModelView): ...@@ -411,6 +412,6 @@ class ModelView(BaseModelView):
'%(count)s models were successfully deleted.', '%(count)s models were successfully deleted.',
count, count,
count=count)) count=count))
except Exception, ex: except Exception as ex:
flash(gettext('Failed to delete models. %(error)s', error=str(ex)), flash(gettext('Failed to delete models. %(error)s', error=str(ex)),
'error') 'error')
...@@ -6,6 +6,7 @@ from peewee import (DateTimeField, DateField, TimeField, ...@@ -6,6 +6,7 @@ from peewee import (DateTimeField, DateField, TimeField,
from wtfpeewee.orm import ModelConverter, model_form from wtfpeewee.orm import ModelConverter, model_form
from flask.ext.admin import form from flask.ext.admin import form
from flask.ext.admin._compat import itervalues
from flask.ext.admin.model.form import InlineFormAdmin, InlineModelConverterBase from flask.ext.admin.model.form import InlineFormAdmin, InlineModelConverterBase
from flask.ext.admin.model.fields import InlineModelFormField, InlineFieldList from flask.ext.admin.model.fields import InlineModelFormField, InlineFieldList
...@@ -167,6 +168,6 @@ class InlineModelConverter(InlineModelConverterBase): ...@@ -167,6 +168,6 @@ class InlineModelConverter(InlineModelConverterBase):
def save_inline(form, model): def save_inline(form, model):
for _, f in form._fields.iteritems(): for f in itervalues(form._fields):
if f.type == 'InlineModelFormList': if f.type == 'InlineModelFormList':
f.save_related(model) f.save_related(model)
...@@ -3,6 +3,7 @@ import logging ...@@ -3,6 +3,7 @@ import logging
from flask import flash from flask import flash
from flask.ext.admin import form from flask.ext.admin import form
from flask.ext.admin._compat import string_types
from flask.ext.admin.babel import gettext, ngettext, lazy_gettext from flask.ext.admin.babel import gettext, ngettext, lazy_gettext
from flask.ext.admin.model import BaseModelView from flask.ext.admin.model import BaseModelView
...@@ -55,7 +56,7 @@ class ModelView(BaseModelView): ...@@ -55,7 +56,7 @@ class ModelView(BaseModelView):
class MyInlineModelConverter(AdminModelConverter): class MyInlineModelConverter(AdminModelConverter):
def post_process(self, form_class, info): def post_process(self, form_class, info):
form_class.value = wtf.TextField('value') form_class.value = TextField('value')
return form_class return form_class
class MyAdminView(ModelView): class MyAdminView(ModelView):
...@@ -173,14 +174,13 @@ class ModelView(BaseModelView): ...@@ -173,14 +174,13 @@ class ModelView(BaseModelView):
def init_search(self): def init_search(self):
if self.column_searchable_list: if self.column_searchable_list:
for p in self.column_searchable_list: for p in self.column_searchable_list:
if isinstance(p, basestring): if isinstance(p, string_types):
p = getattr(self.model, p) p = getattr(self.model, p)
field_type = type(p) field_type = type(p)
# Check type # Check type
if (field_type != CharField and if (field_type != CharField and field_type != TextField):
field_type != TextField):
raise Exception('Can only search on text columns. ' + raise Exception('Can only search on text columns. ' +
'Failed to setup search for "%s"' % p) 'Failed to setup search for "%s"' % p)
...@@ -189,7 +189,7 @@ class ModelView(BaseModelView): ...@@ -189,7 +189,7 @@ class ModelView(BaseModelView):
return bool(self._search_fields) return bool(self._search_fields)
def scaffold_filters(self, name): def scaffold_filters(self, name):
if isinstance(name, basestring): if isinstance(name, string_types):
attr = getattr(self.model, name, None) attr = getattr(self.model, name, None)
else: else:
attr = name attr = name
...@@ -202,7 +202,7 @@ class ModelView(BaseModelView): ...@@ -202,7 +202,7 @@ class ModelView(BaseModelView):
visible_name = '%s / %s' % (self.get_column_name(attr.model_class.__name__), visible_name = '%s / %s' % (self.get_column_name(attr.model_class.__name__),
self.get_column_name(attr.name)) self.get_column_name(attr.name))
else: else:
if not isinstance(name, basestring): if not isinstance(name, string_types):
visible_name = self.get_column_name(attr.name) visible_name = self.get_column_name(attr.name)
else: else:
visible_name = self.get_column_name(name) visible_name = self.get_column_name(name)
...@@ -253,7 +253,7 @@ class ModelView(BaseModelView): ...@@ -253,7 +253,7 @@ class ModelView(BaseModelView):
return query return query
def _order_by(self, query, joins, sort_field, sort_desc): def _order_by(self, query, joins, sort_field, sort_desc):
if isinstance(sort_field, basestring): if isinstance(sort_field, string_types):
field = getattr(self.model, sort_field) field = getattr(self.model, sort_field)
query = query.order_by(field.desc() if sort_desc else field.asc()) query = query.order_by(field.desc() if sort_desc else field.asc())
elif isinstance(sort_field, Field): elif isinstance(sort_field, Field):
...@@ -341,7 +341,7 @@ class ModelView(BaseModelView): ...@@ -341,7 +341,7 @@ class ModelView(BaseModelView):
# For peewee have to save inline forms after model was saved # For peewee have to save inline forms after model was saved
save_inline(form, model) save_inline(form, model)
except Exception, ex: except Exception as ex:
flash(gettext('Failed to create model. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to create model. %(error)s', error=str(ex)), 'error')
logging.exception('Failed to create model') logging.exception('Failed to create model')
return False return False
...@@ -358,7 +358,7 @@ class ModelView(BaseModelView): ...@@ -358,7 +358,7 @@ class ModelView(BaseModelView):
# For peewee have to save inline forms after model was saved # For peewee have to save inline forms after model was saved
save_inline(form, model) save_inline(form, model)
except Exception, ex: except Exception as ex:
flash(gettext('Failed to update model. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to update model. %(error)s', error=str(ex)), 'error')
logging.exception('Failed to update model') logging.exception('Failed to update model')
return False return False
...@@ -372,7 +372,7 @@ class ModelView(BaseModelView): ...@@ -372,7 +372,7 @@ class ModelView(BaseModelView):
self.on_model_delete(model) self.on_model_delete(model)
model.delete_instance(recursive=True) model.delete_instance(recursive=True)
return True return True
except Exception, ex: except Exception as ex:
flash(gettext('Failed to delete model. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to delete model. %(error)s', error=str(ex)), 'error')
logging.exception('Failed to delete model') logging.exception('Failed to delete model')
return False return False
...@@ -407,5 +407,5 @@ class ModelView(BaseModelView): ...@@ -407,5 +407,5 @@ class ModelView(BaseModelView):
'%(count)s models were successfully deleted.', '%(count)s models were successfully deleted.',
count, count,
count=count)) count=count))
except Exception, ex: except Exception as ex:
flash(gettext('Failed to delete models. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to delete models. %(error)s', error=str(ex)), 'error')
...@@ -5,8 +5,8 @@ from bson import ObjectId ...@@ -5,8 +5,8 @@ from bson import ObjectId
from bson.errors import InvalidId from bson.errors import InvalidId
from flask import flash from flask import flash
from jinja2 import contextfunction
from flask.ext.admin._compat import string_types
from flask.ext.admin.babel import gettext, ngettext, lazy_gettext from flask.ext.admin.babel import gettext, ngettext, lazy_gettext
from flask.ext.admin.model import BaseModelView from flask.ext.admin.model import BaseModelView
from flask.ext.admin.actions import action from flask.ext.admin.actions import action
...@@ -92,7 +92,7 @@ class ModelView(BaseModelView): ...@@ -92,7 +92,7 @@ class ModelView(BaseModelView):
""" """
if self.column_searchable_list: if self.column_searchable_list:
for p in self.column_searchable_list: for p in self.column_searchable_list:
if not isinstance(p, basestring): if not isinstance(p, string_types):
raise ValueError('Expected string') raise ValueError('Expected string')
# TODO: Validation? # TODO: Validation?
...@@ -206,7 +206,7 @@ class ModelView(BaseModelView): ...@@ -206,7 +206,7 @@ class ModelView(BaseModelView):
if sort_column: if sort_column:
sort_by = [(sort_column, pymongo.DESCENDING if sort_desc else pymongo.ASCENDING)] sort_by = [(sort_column, pymongo.DESCENDING if sort_desc else pymongo.ASCENDING)]
else: else:
order = self.get_default_order() order = self._get_default_order()
if order: if order:
sort_by = [(order[0], pymongo.DESCENDING if order[1] else pymongo.ASCENDING)] sort_by = [(order[0], pymongo.DESCENDING if order[1] else pymongo.ASCENDING)]
...@@ -256,7 +256,7 @@ class ModelView(BaseModelView): ...@@ -256,7 +256,7 @@ class ModelView(BaseModelView):
model = form.data model = form.data
self.on_model_change(form, model) self.on_model_change(form, model)
self.coll.insert(model) self.coll.insert(model)
except Exception, ex: except Exception as ex:
flash(gettext('Failed to create model. %(error)s', error=str(ex)), flash(gettext('Failed to create model. %(error)s', error=str(ex)),
'error') 'error')
logging.exception('Failed to create model') logging.exception('Failed to create model')
...@@ -281,7 +281,7 @@ class ModelView(BaseModelView): ...@@ -281,7 +281,7 @@ class ModelView(BaseModelView):
pk = self.get_pk_value(model) pk = self.get_pk_value(model)
self.coll.update({'_id': pk}, model) self.coll.update({'_id': pk}, model)
except Exception, ex: except Exception as ex:
flash(gettext('Failed to update model. %(error)s', error=str(ex)), flash(gettext('Failed to update model. %(error)s', error=str(ex)),
'error') 'error')
logging.exception('Failed to update model') logging.exception('Failed to update model')
...@@ -307,7 +307,7 @@ class ModelView(BaseModelView): ...@@ -307,7 +307,7 @@ class ModelView(BaseModelView):
self.on_model_delete(model) self.on_model_delete(model)
self.coll.remove({'_id': pk}) self.coll.remove({'_id': pk})
return True return True
except Exception, ex: except Exception as ex:
flash(gettext('Failed to delete model. %(error)s', error=str(ex)), flash(gettext('Failed to delete model. %(error)s', error=str(ex)),
'error') 'error')
logging.exception('Failed to delete model') logging.exception('Failed to delete model')
...@@ -337,6 +337,6 @@ class ModelView(BaseModelView): ...@@ -337,6 +337,6 @@ class ModelView(BaseModelView):
'%(count)s models were successfully deleted.', '%(count)s models were successfully deleted.',
count, count,
count=count)) count=count))
except Exception, ex: except Exception as ex:
flash(gettext('Failed to delete models. %(error)s', error=str(ex)), flash(gettext('Failed to delete models. %(error)s', error=str(ex)),
'error') 'error')
...@@ -8,6 +8,7 @@ from wtforms.fields import SelectFieldBase ...@@ -8,6 +8,7 @@ from wtforms.fields import SelectFieldBase
from wtforms.validators import ValidationError from wtforms.validators import ValidationError
from .tools import get_primary_key from .tools import get_primary_key
from flask.ext.admin._compat import text_type, string_types
from flask.ext.admin.model.fields import InlineFieldList, InlineModelFormField from flask.ext.admin.model.fields import InlineFieldList, InlineModelFormField
...@@ -58,14 +59,14 @@ class QuerySelectField(SelectFieldBase): ...@@ -58,14 +59,14 @@ class QuerySelectField(SelectFieldBase):
if get_pk is None: if get_pk is None:
if not has_identity_key: if not has_identity_key:
raise Exception('The sqlalchemy identity_key function could not be imported.') raise Exception(u'The sqlalchemy identity_key function could not be imported.')
self.get_pk = get_pk_from_identity self.get_pk = get_pk_from_identity
else: else:
self.get_pk = get_pk self.get_pk = get_pk
if get_label is None: if get_label is None:
self.get_label = lambda x: x self.get_label = lambda x: x
elif isinstance(get_label, basestring): elif isinstance(get_label, string_types):
self.get_label = operator.attrgetter(get_label) self.get_label = operator.attrgetter(get_label)
else: else:
self.get_label = get_label self.get_label = get_label
...@@ -93,7 +94,7 @@ class QuerySelectField(SelectFieldBase): ...@@ -93,7 +94,7 @@ class QuerySelectField(SelectFieldBase):
if self._object_list is None: if self._object_list is None:
query = self.query or self.query_factory() query = self.query or self.query_factory()
get_pk = self.get_pk get_pk = self.get_pk
self._object_list = list((unicode(get_pk(obj)), obj) for obj in query) self._object_list = [(text_type(get_pk(obj)), obj) for obj in query]
return self._object_list return self._object_list
def iter_choices(self): def iter_choices(self):
...@@ -172,7 +173,7 @@ class QuerySelectMultipleField(QuerySelectField): ...@@ -172,7 +173,7 @@ class QuerySelectMultipleField(QuerySelectField):
obj_list = list(x[1] for x in self._get_object_list()) obj_list = list(x[1] for x in self._get_object_list())
for v in self.data: for v in self.data:
if v not in obj_list: if v not in obj_list:
raise ValidationError(self.gettext('Not a valid choice')) raise ValidationError(self.gettext(u'Not a valid choice'))
class InlineModelFormList(InlineFieldList): class InlineModelFormList(InlineFieldList):
...@@ -238,4 +239,4 @@ class InlineModelFormList(InlineFieldList): ...@@ -238,4 +239,4 @@ class InlineModelFormList(InlineFieldList):
def get_pk_from_identity(obj): def get_pk_from_identity(obj):
# TODO: Remove me # TODO: Remove me
cls, key = identity_key(instance=obj) cls, key = identity_key(instance=obj)
return u':'.join(unicode(x) for x in key) return u':'.join(text_type(x) for x in key)
...@@ -26,7 +26,8 @@ class Unique(object): ...@@ -26,7 +26,8 @@ class Unique(object):
def __call__(self, form, field): def __call__(self, form, field):
try: try:
obj = (self.db_session.query(self.model) obj = (self.db_session.query(self.model)
.filter(self.column == field.data).one()) .filter(self.column == field.data)
.one())
if not hasattr(form, '_obj') or not form._obj == obj: if not hasattr(form, '_obj') or not form._obj == obj:
if self.message is None: if self.message is None:
......
...@@ -7,6 +7,7 @@ from sqlalchemy import or_, Column, func ...@@ -7,6 +7,7 @@ from sqlalchemy import or_, Column, func
from flask import flash from flask import flash
from flask.ext.admin._compat import string_types
from flask.ext.admin.tools import ObsoleteAttr from flask.ext.admin.tools import ObsoleteAttr
from flask.ext.admin.babel import gettext, ngettext, lazy_gettext from flask.ext.admin.babel import gettext, ngettext, lazy_gettext
from flask.ext.admin.model import BaseModelView from flask.ext.admin.model import BaseModelView
...@@ -348,7 +349,7 @@ class ModelView(BaseModelView): ...@@ -348,7 +349,7 @@ class ModelView(BaseModelView):
return columns return columns
def _get_columns_for_field(self, field): def _get_columns_for_field(self, field):
if isinstance(field, basestring): if isinstance(field, string_types):
attr = getattr(self.model, field, None) attr = getattr(self.model, field, None)
if field is None: if field is None:
...@@ -410,7 +411,7 @@ class ModelView(BaseModelView): ...@@ -410,7 +411,7 @@ class ModelView(BaseModelView):
""" """
join_tables = [] join_tables = []
if isinstance(name, basestring): if isinstance(name, string_types):
model = self.model model = self.model
for attribute in name.split('.'): for attribute in name.split('.'):
...@@ -474,7 +475,7 @@ class ModelView(BaseModelView): ...@@ -474,7 +475,7 @@ class ModelView(BaseModelView):
self.get_column_name(column.name) self.get_column_name(column.name)
) )
else: else:
if not isinstance(name, basestring): if not isinstance(name, string_types):
visible_name = self.get_column_name(name.property.key) visible_name = self.get_column_name(name.property.key)
else: else:
visible_name = self.get_column_name(name) visible_name = self.get_column_name(name)
...@@ -596,7 +597,7 @@ class ModelView(BaseModelView): ...@@ -596,7 +597,7 @@ class ModelView(BaseModelView):
""" """
# TODO: Preprocessing for joins # TODO: Preprocessing for joins
# Try to handle it as a string # Try to handle it as a string
if isinstance(sort_field, basestring): if isinstance(sort_field, string_types):
# Create automatic join against a table if column name # Create automatic join against a table if column name
# contains dot. # contains dot.
if '.' in sort_field: if '.' in sort_field:
...@@ -637,7 +638,7 @@ class ModelView(BaseModelView): ...@@ -637,7 +638,7 @@ class ModelView(BaseModelView):
if order is not None: if order is not None:
field, direction = order field, direction = order
if isinstance(field, basestring): if isinstance(field, string_types):
field = getattr(self.model, field) field = getattr(self.model, field)
return field, direction return field, direction
...@@ -764,7 +765,7 @@ class ModelView(BaseModelView): ...@@ -764,7 +765,7 @@ class ModelView(BaseModelView):
self.session.add(model) self.session.add(model)
self.on_model_change(form, model) self.on_model_change(form, model)
self.session.commit() self.session.commit()
except Exception, ex: except Exception as ex:
flash(gettext('Failed to create model. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to create model. %(error)s', error=str(ex)), 'error')
logging.exception('Failed to create model') logging.exception('Failed to create model')
self.session.rollback() self.session.rollback()
...@@ -787,7 +788,7 @@ class ModelView(BaseModelView): ...@@ -787,7 +788,7 @@ class ModelView(BaseModelView):
form.populate_obj(model) form.populate_obj(model)
self.on_model_change(form, model) self.on_model_change(form, model)
self.session.commit() self.session.commit()
except Exception, ex: except Exception as ex:
flash(gettext('Failed to update model. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to update model. %(error)s', error=str(ex)), 'error')
logging.exception('Failed to update model') logging.exception('Failed to update model')
self.session.rollback() self.session.rollback()
...@@ -810,7 +811,7 @@ class ModelView(BaseModelView): ...@@ -810,7 +811,7 @@ class ModelView(BaseModelView):
self.session.delete(model) self.session.delete(model)
self.session.commit() self.session.commit()
return True return True
except Exception, ex: except Exception as ex:
flash(gettext('Failed to delete model. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to delete model. %(error)s', error=str(ex)), 'error')
logging.exception('Failed to delete model') logging.exception('Failed to delete model')
self.session.rollback() self.session.rollback()
...@@ -848,5 +849,5 @@ class ModelView(BaseModelView): ...@@ -848,5 +849,5 @@ class ModelView(BaseModelView):
'%(count)s models were successfully deleted.', '%(count)s models were successfully deleted.',
count, count,
count=count)) count=count))
except Exception, ex: except Exception as ex:
flash(gettext('Failed to delete models. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to delete models. %(error)s', error=str(ex)), 'error')
import time import time
import datetime import datetime
from wtforms import fields, widgets from wtforms import form, fields, widgets
from flask.globals import _request_ctx_stack from flask.globals import _request_ctx_stack
from flask.ext import wtf
from flask.ext.admin.babel import gettext, ngettext from flask.ext.admin.babel import gettext, ngettext
from flask.ext.admin import helpers as h from flask.ext.admin import helpers as h
from flask.ext.admin._compat import text_type
class BaseForm(wtf.Form): # TODO: Use flask.ext.wtf if possible
""" class BaseForm(form.Form):
Customized form class. def __init__(self, formdata=None, obj=None, prefix=u'', **kwargs):
"""
def __init__(self, formdata=None, obj=None, prefix='', **kwargs):
if formdata:
super(BaseForm, self).__init__(formdata, obj, prefix, **kwargs)
else:
super(BaseForm, self).__init__(obj=obj, prefix=prefix, **kwargs)
self._obj = obj self._obj = obj
@property super(BaseForm, self).__init__(formdata=formdata, obj=obj, prefix=prefix, **kwargs)
def has_file_field(self):
"""
Return True if form contains at least one FileField.
Does not check for child form fields.
"""
# TODO: Optimize me
for f in self:
if isinstance(f, wtf.FileField):
return True
return False
class TimeField(fields.Field): class TimeField(fields.Field):
...@@ -110,7 +92,7 @@ class Select2Field(fields.SelectField): ...@@ -110,7 +92,7 @@ class Select2Field(fields.SelectField):
""" """
widget = Select2Widget() widget = Select2Widget()
def __init__(self, label=None, validators=None, coerce=unicode, def __init__(self, label=None, validators=None, coerce=text_type,
choices=None, allow_blank=False, blank_text=None, **kwargs): choices=None, allow_blank=False, blank_text=None, **kwargs):
super(Select2Field, self).__init__( super(Select2Field, self).__init__(
label, validators, coerce, choices, **kwargs label, validators, coerce, choices, **kwargs
......
from flask import g from flask import g, request
from wtforms.validators import DataRequired, InputRequired from wtforms.validators import DataRequired, InputRequired
...@@ -7,11 +7,49 @@ def set_current_view(view): ...@@ -7,11 +7,49 @@ def set_current_view(view):
def get_current_view(): def get_current_view():
"""
Get current administrative view.
"""
return getattr(g, '_admin_view', None) return getattr(g, '_admin_view', None)
def is_required_form_field(field): def is_required_form_field(field):
"""
Check if form field has `DataRequired` or `InputRequired` validators.
:param field:
WTForms field to check
"""
for validator in field.validators: for validator in field.validators:
if isinstance(validator, (DataRequired, InputRequired)): if isinstance(validator, (DataRequired, InputRequired)):
return True return True
return False return False
def is_form_submitted():
"""
Check if current method is PUT or POST
"""
return request and request.method in ("PUT", "POST")
def validate_form_on_submit(form):
"""
If current method is PUT or POST, validate form and return validation status.
"""
return is_form_submitted() and form.validate()
def get_form_data():
"""
If current method is PUT or POST, return concatenated `request.form` with
`request.files` or `None` otherwise.
"""
if is_form_submitted():
formdata = request.form
if request.files:
formdata = formdata.copy()
formdata.update(request.files)
return formdata
return None
...@@ -10,6 +10,7 @@ from flask.ext.admin.base import BaseView, expose ...@@ -10,6 +10,7 @@ from flask.ext.admin.base import BaseView, expose
from flask.ext.admin.tools import rec_getattr, ObsoleteAttr from flask.ext.admin.tools import rec_getattr, ObsoleteAttr
from flask.ext.admin.model import filters, typefmt from flask.ext.admin.model import filters, typefmt
from flask.ext.admin.actions import ActionsMixin from flask.ext.admin.actions import ActionsMixin
from flask.ext.admin.helpers import get_form_data, validate_form_on_submit
class BaseModelView(BaseView, ActionsMixin): class BaseModelView(BaseView, ActionsMixin):
...@@ -246,7 +247,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -246,7 +247,7 @@ class BaseModelView(BaseView, ActionsMixin):
For example:: For example::
class MyForm(wtf.Form): class MyForm(Form):
pass pass
class MyModelView(BaseModelView): class MyModelView(BaseModelView):
...@@ -262,7 +263,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -262,7 +263,7 @@ class BaseModelView(BaseView, ActionsMixin):
class MyModelView(BaseModelView): class MyModelView(BaseModelView):
form_args = dict( form_args = dict(
name=dict(label='First Name', validators=[wtf.required()]) name=dict(label='First Name', validators=[required()])
) )
""" """
...@@ -607,7 +608,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -607,7 +608,7 @@ class BaseModelView(BaseView, ActionsMixin):
Override to implement custom behavior. Override to implement custom behavior.
""" """
return self._create_form_class(obj=obj) return self._create_form_class(get_form_data(), obj=obj)
def edit_form(self, obj=None): def edit_form(self, obj=None):
""" """
...@@ -615,7 +616,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -615,7 +616,7 @@ class BaseModelView(BaseView, ActionsMixin):
Override to implement custom behavior. Override to implement custom behavior.
""" """
return self._edit_form_class(obj=obj) return self._edit_form_class(get_form_data(), obj=obj)
# Helpers # Helpers
def is_sortable(self, name): def is_sortable(self, name):
...@@ -646,8 +647,6 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -646,8 +647,6 @@ class BaseModelView(BaseView, ActionsMixin):
else: else:
return self.column_default_sort, False return self.column_default_sort, False
return field, direction
return None return None
# Database-related API # Database-related API
...@@ -936,7 +935,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -936,7 +935,7 @@ class BaseModelView(BaseView, ActionsMixin):
search, filters) search, filters)
# Calculate number of pages # Calculate number of pages
num_pages = count / self.page_size num_pages = count // self.page_size
if count % self.page_size != 0: if count % self.page_size != 0:
num_pages += 1 num_pages += 1
...@@ -1013,8 +1012,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1013,8 +1012,7 @@ class BaseModelView(BaseView, ActionsMixin):
# Actions # Actions
actions=actions, actions=actions,
actions_confirmation=actions_confirmation actions_confirmation=actions_confirmation)
)
@expose('/new/', methods=('GET', 'POST')) @expose('/new/', methods=('GET', 'POST'))
def create_view(self): def create_view(self):
...@@ -1028,7 +1026,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1028,7 +1026,7 @@ class BaseModelView(BaseView, ActionsMixin):
form = self.create_form() form = self.create_form()
if form.validate_on_submit(): if validate_form_on_submit(form):
if self.create_model(form): if self.create_model(form):
if '_add_another' in request.form: if '_add_another' in request.form:
flash(gettext('Model was successfully created.')) flash(gettext('Model was successfully created.'))
...@@ -1062,7 +1060,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1062,7 +1060,7 @@ class BaseModelView(BaseView, ActionsMixin):
form = self.edit_form(obj=model) form = self.edit_form(obj=model)
if form.validate_on_submit(): if validate_form_on_submit(form):
if self.update_model(form, model): if self.update_model(form, model):
return redirect(return_url) return redirect(return_url)
......
...@@ -2,6 +2,7 @@ import itertools ...@@ -2,6 +2,7 @@ import itertools
from wtforms.fields import FieldList, FormField from wtforms.fields import FieldList, FormField
from flask.ext.admin._compat import iteritems
from .widgets import InlineFieldListWidget, InlineFormWidget from .widgets import InlineFieldListWidget, InlineFormWidget
...@@ -22,9 +23,9 @@ class InlineFieldList(FieldList): ...@@ -22,9 +23,9 @@ class InlineFieldList(FieldList):
def __call__(self, **kwargs): def __call__(self, **kwargs):
return self.widget(self, return self.widget(self,
template=self.template, template=self.template,
check=self.display_row_controls, check=self.display_row_controls,
**kwargs) **kwargs)
def display_row_controls(self, field): def display_row_controls(self, field):
return True return True
...@@ -82,7 +83,7 @@ class InlineModelFormField(FormField): ...@@ -82,7 +83,7 @@ class InlineModelFormField(FormField):
return getattr(self.form, self._pk).data return getattr(self.form, self._pk).data
def populate_obj(self, obj, name): def populate_obj(self, obj, name):
for name, field in self.form._fields.iteritems(): for name, field in iteritems(self.form._fields):
if name != self._pk: if name != self._pk:
field.populate_obj(obj, name) field.populate_obj(obj, name)
......
from flask.ext.admin._compat import text_type
from flask.ext.admin.babel import lazy_gettext from flask.ext.admin.babel import lazy_gettext
...@@ -30,7 +31,7 @@ class BaseFilter(object): ...@@ -30,7 +31,7 @@ class BaseFilter(object):
Associated administrative view class. Associated administrative view class.
""" """
if self.options: if self.options:
return [(v, unicode(n)) for v, n in self.options] return [(v, text_type(n)) for v, n in self.options]
return None return None
...@@ -82,8 +83,8 @@ class BaseBooleanFilter(BaseFilter): ...@@ -82,8 +83,8 @@ class BaseBooleanFilter(BaseFilter):
""" """
def __init__(self, name, options=None, data_type=None): def __init__(self, name, options=None, data_type=None):
super(BaseBooleanFilter, self).__init__(name, super(BaseBooleanFilter, self).__init__(name,
(('1', lazy_gettext('Yes')), (('1', lazy_gettext(u'Yes')),
('0', lazy_gettext('No'))), ('0', lazy_gettext(u'No'))),
data_type) data_type)
def validate(self, value): def validate(self, value):
......
import inspect import inspect
from flask.ext.admin.form import BaseForm from flask.ext.admin.form import BaseForm
from flask.ext.admin._compat import iteritems
def converts(*args): def converts(*args):
...@@ -37,7 +38,7 @@ class InlineFormAdmin(object): ...@@ -37,7 +38,7 @@ class InlineFormAdmin(object):
if not hasattr(self, k): if not hasattr(self, k):
setattr(self, k, None) setattr(self, k, None)
for k, v in kwargs.iteritems(): for k, v in iteritems(kwargs):
setattr(self, k, v) setattr(self, k, v)
def postprocess_form(self, form_class): def postprocess_form(self, form_class):
...@@ -48,7 +49,7 @@ class InlineFormAdmin(object): ...@@ -48,7 +49,7 @@ class InlineFormAdmin(object):
class MyInlineForm(InlineFormAdmin): class MyInlineForm(InlineFormAdmin):
def postprocess_form(self, form): def postprocess_form(self, form):
form.value = wtf.TextField('value') form.value = TextField('value')
return form return form
class MyAdmin(ModelView): class MyAdmin(ModelView):
......
from jinja2 import Markup from jinja2 import Markup
from flask.ext.admin._compat import text_type
def null_formatter(view, value): def null_formatter(view, value):
...@@ -38,7 +39,7 @@ def list_formatter(view, values): ...@@ -38,7 +39,7 @@ def list_formatter(view, values):
:param values: :param values:
Value to check Value to check
""" """
return u', '.join(unicode(v) for v in values) return u', '.join(text_type(v) for v in values)
BASE_FORMATTERS = { BASE_FORMATTERS = {
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
this.addInlineField = function(id, el, template) { this.addInlineField = function(id, el, template) {
var $el = $(el); var $el = $(el);
var $template = $(template); var $template = $($(template).html());
// Figure out new field ID // Figure out new field ID
var lastField = $el.children('.fa-inline-field').last(); var lastField = $el.children('.fa-inline-field').last();
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
var prefix = id + '-0'; var prefix = id + '-0';
if (lastField.length > 0) { if (lastField.length > 0) {
var parts = $(lastField[0]).attr('id').split('-'); var parts = $(lastField[0]).attr('id').split('-');
idx = parseInt(parts[parts.length - 1]) + 1; idx = parseInt(parts[parts.length - 1], 10) + 1;
prefix = id + '-' + idx; prefix = id + '-' + idx;
} }
......
{% macro render_template(template, render) -%}
<div class="fa-inline-field">
<div class="fa-inline-field-control">
<a href="javascript:void(0)" class="fa-remove-field"><i class="icon-remove"></i></a>
</div>
{{ render(template) }}
</div>
{%- endmacro %}
{% macro render_inline_fields(field, template, render, check=None) %} {% macro render_inline_fields(field, template, render, check=None) %}
<div class="well"> <div class="well">
<div id="{{ field.id }}-fields"> <div id="{{ field.id }}-fields">
...@@ -23,6 +14,14 @@ ...@@ -23,6 +14,14 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<a href="javascript:void(0)" class="btn" onclick="faForm.addInlineField('{{ field.id }}', '#{{ field.id }}-fields', {{ render_template(template, render)|tojson }});">{{ _gettext('Add') }} {{ field.label.text }}</a> <div id="{{ field.id }}-template" class="hide">
<div class="fa-inline-field">
<div class="fa-inline-field-control">
<a href="javascript:void(0)" class="fa-remove-field"><i class="icon-remove"></i></a>
</div>
{{ render(template) }}
</div>
</div>
<a id="{{ field.id }}-button" href="javascript:void(0)" class="btn" onclick="faForm.addInlineField('{{ field.id }}', '#{{ field.id }}-fields', '#{{ field.id }}-template');">{{ _gettext('Add') }} {{ field.label.text }}</a>
</div> </div>
{% endmacro %} {% endmacro %}
from flask import Flask
from flask.ext.admin import Admin
def setup():
app = Flask(__name__)
app.config['SECRET_KEY'] = '1'
app.config['CSRF_ENABLED'] = False
admin = Admin(app)
return app, admin
from nose.tools import eq_, ok_
import os.path as op
from flask.ext.admin.contrib import fileadmin
from . import setup
def create_view():
app, admin = setup()
path = op.join(op.dirname(__file__), 'files')
view = fileadmin.FileAdmin(path, '/files/', name='Files')
admin.add_view(view)
return app, admin, view
def test_file_admin():
app, admin, view = create_view()
client = app.test_client()
rv = client.get('/admin/fileadmin/')
eq_(rv.status_code, 200)
ok_('dummy.txt' in rv.data.decode('utf-8'))
# TODO: Check actions, etc
from nose.tools import eq_, ok_ from nose.tools import eq_, ok_
from flask.ext import wtf from wtforms import fields
from flask.ext.admin.contrib.mongoengine import ModelView from flask.ext.admin.contrib.mongoengine import ModelView
from . import setup from . import setup
...@@ -62,14 +63,14 @@ def test_model(): ...@@ -62,14 +63,14 @@ def test_model():
# Verify form # Verify form
# TODO: Figure out why there's inconsistency # TODO: Figure out why there's inconsistency
try: try:
eq_(view._create_form_class.test1.field_class, wtf.TextField) eq_(view._create_form_class.test1.field_class, fields.TextField)
eq_(view._create_form_class.test2.field_class, wtf.TextField) eq_(view._create_form_class.test2.field_class, fields.TextField)
except AssertionError: except AssertionError:
eq_(view._create_form_class.test1.field_class, wtf.StringField) eq_(view._create_form_class.test1.field_class, fields.StringField)
eq_(view._create_form_class.test2.field_class, wtf.StringField) eq_(view._create_form_class.test2.field_class, fields.StringField)
eq_(view._create_form_class.test3.field_class, wtf.TextAreaField) eq_(view._create_form_class.test3.field_class, fields.TextAreaField)
eq_(view._create_form_class.test4.field_class, wtf.TextAreaField) eq_(view._create_form_class.test4.field_class, fields.TextAreaField)
# Make some test clients # Make some test clients
client = app.test_client() client = app.test_client()
......
...@@ -2,7 +2,9 @@ from nose.tools import eq_, ok_ ...@@ -2,7 +2,9 @@ from nose.tools import eq_, ok_
import peewee import peewee
from flask.ext import wtf from wtforms import fields
from flask.ext.admin._compat import iteritems
from flask.ext.admin.contrib.peeweemodel import ModelView from flask.ext.admin.contrib.peeweemodel import ModelView
from . import setup from . import setup
...@@ -12,7 +14,7 @@ class CustomModelView(ModelView): ...@@ -12,7 +14,7 @@ class CustomModelView(ModelView):
def __init__(self, model, def __init__(self, model,
name=None, category=None, endpoint=None, url=None, name=None, category=None, endpoint=None, url=None,
**kwargs): **kwargs):
for k, v in kwargs.iteritems(): for k, v in iteritems(kwargs):
setattr(self, k, v) setattr(self, k, v)
super(CustomModelView, self).__init__(model, super(CustomModelView, self).__init__(model,
...@@ -73,10 +75,10 @@ def test_model(): ...@@ -73,10 +75,10 @@ def test_model():
eq_(view._filters, None) eq_(view._filters, None)
# Verify form # Verify form
eq_(view._create_form_class.test1.field_class, wtf.TextField) eq_(view._create_form_class.test1.field_class, fields.TextField)
eq_(view._create_form_class.test2.field_class, wtf.TextField) eq_(view._create_form_class.test2.field_class, fields.TextField)
eq_(view._create_form_class.test3.field_class, wtf.TextAreaField) eq_(view._create_form_class.test3.field_class, fields.TextAreaField)
eq_(view._create_form_class.test4.field_class, wtf.TextAreaField) eq_(view._create_form_class.test4.field_class, fields.TextAreaField)
# Make some test clients # Make some test clients
client = app.test_client() client = app.test_client()
......
from nose.tools import eq_, ok_, raises from nose.tools import eq_, ok_, raises
from flask.ext import wtf from wtforms import fields
from flask.ext.admin._compat import iteritems
from flask.ext.admin.contrib.sqlamodel import ModelView from flask.ext.admin.contrib.sqlamodel import ModelView
from . import setup from . import setup
...@@ -10,7 +12,7 @@ class CustomModelView(ModelView): ...@@ -10,7 +12,7 @@ class CustomModelView(ModelView):
def __init__(self, model, session, def __init__(self, model, session,
name=None, category=None, endpoint=None, url=None, name=None, category=None, endpoint=None, url=None,
**kwargs): **kwargs):
for k, v in kwargs.iteritems(): for k, v in iteritems(kwargs):
setattr(self, k, v) setattr(self, k, v)
super(CustomModelView, self).__init__(model, session, super(CustomModelView, self).__init__(model, session,
...@@ -82,10 +84,10 @@ def test_model(): ...@@ -82,10 +84,10 @@ def test_model():
eq_(view._filters, None) eq_(view._filters, None)
# Verify form # Verify form
eq_(view._create_form_class.test1.field_class, wtf.TextField) eq_(view._create_form_class.test1.field_class, fields.TextField)
eq_(view._create_form_class.test2.field_class, wtf.TextField) eq_(view._create_form_class.test2.field_class, fields.TextField)
eq_(view._create_form_class.test3.field_class, wtf.TextAreaField) eq_(view._create_form_class.test3.field_class, fields.TextAreaField)
eq_(view._create_form_class.test4.field_class, wtf.TextAreaField) eq_(view._create_form_class.test4.field_class, fields.TextAreaField)
# Make some test clients # Make some test clients
client = app.test_client() client = app.test_client()
...@@ -101,14 +103,14 @@ def test_model(): ...@@ -101,14 +103,14 @@ def test_model():
eq_(rv.status_code, 302) eq_(rv.status_code, 302)
model = db.session.query(Model1).first() model = db.session.query(Model1).first()
eq_(model.test1, 'test1large') eq_(model.test1, u'test1large')
eq_(model.test2, 'test2') eq_(model.test2, u'test2')
eq_(model.test3, '') eq_(model.test3, u'')
eq_(model.test4, '') eq_(model.test4, u'')
rv = client.get('/admin/model1view/') rv = client.get('/admin/model1view/')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
ok_('test1large' in rv.data) ok_(u'test1large' in rv.data.decode('utf-8'))
url = '/admin/model1view/edit/?id=%s' % model.id url = '/admin/model1view/edit/?id=%s' % model.id
rv = client.get(url) rv = client.get(url)
...@@ -157,8 +159,9 @@ def test_list_columns(): ...@@ -157,8 +159,9 @@ def test_list_columns():
client = app.test_client() client = app.test_client()
rv = client.get('/admin/model1view/') rv = client.get('/admin/model1view/')
ok_('Column1' in rv.data) data = rv.data.decode('utf-8')
ok_('Test2' not in rv.data) ok_('Column1' in data)
ok_('Test2' not in data)
def test_exclude_columns(): def test_exclude_columns():
...@@ -168,7 +171,7 @@ def test_exclude_columns(): ...@@ -168,7 +171,7 @@ def test_exclude_columns():
view = CustomModelView( view = CustomModelView(
Model1, db.session, Model1, db.session,
column_exclude_list=['test2', 'test4', 'enum_field'] column_exclude_list=['test2', 'test4', 'enum_field']
) )
admin.add_view(view) admin.add_view(view)
...@@ -180,8 +183,9 @@ def test_exclude_columns(): ...@@ -180,8 +183,9 @@ def test_exclude_columns():
client = app.test_client() client = app.test_client()
rv = client.get('/admin/model1view/') rv = client.get('/admin/model1view/')
ok_('Test1' in rv.data) data = rv.data.decode('utf-8')
ok_('Test2' not in rv.data) ok_('Test1' in data)
ok_('Test2' not in data)
def test_column_searchable_list(): def test_column_searchable_list():
...@@ -207,8 +211,9 @@ def test_column_searchable_list(): ...@@ -207,8 +211,9 @@ def test_column_searchable_list():
client = app.test_client() client = app.test_client()
rv = client.get('/admin/model1view/?search=model1') rv = client.get('/admin/model1view/?search=model1')
ok_('model1' in rv.data) data = rv.data.decode('utf-8')
ok_('model2' not in rv.data) ok_('model1' in data)
ok_('model2' not in data)
def test_column_filters(): def test_column_filters():
...@@ -225,24 +230,23 @@ def test_column_filters(): ...@@ -225,24 +230,23 @@ def test_column_filters():
eq_(len(view._filters), 4) eq_(len(view._filters), 4)
eq_(view._filter_dict, { eq_(view._filter_dict, {
'Test1': [ u'Test1': [
(0, 'equals'), (0, u'equals'),
(1, 'not equal'), (1, u'not equal'),
(2, 'contains'), (2, u'contains'),
(3, 'not contains') (3, u'not contains')
], ]})
})
# Test filter that references property # Test filter that references property
view = CustomModelView(Model2, db.session, view = CustomModelView(Model2, db.session,
column_filters=['model1']) column_filters=['model1'])
eq_(view._filter_dict, { eq_(view._filter_dict, {
'Model1 / Test1': [ u'Model1 / Test1': [
(0, 'equals'), (0, u'equals'),
(1, 'not equal'), (1, u'not equal'),
(2, 'contains'), (2, u'contains'),
(3, 'not contains') (3, u'not contains')
], ],
'Model1 / Test2': [ 'Model1 / Test2': [
(4, 'equals'), (4, 'equals'),
...@@ -250,27 +254,26 @@ def test_column_filters(): ...@@ -250,27 +254,26 @@ def test_column_filters():
(6, 'contains'), (6, 'contains'),
(7, 'not contains') (7, 'not contains')
], ],
'Model1 / Test3': [ u'Model1 / Test3': [
(8, 'equals'), (8, u'equals'),
(9, 'not equal'), (9, u'not equal'),
(10, 'contains'), (10, u'contains'),
(11, 'not contains') (11, u'not contains')
], ],
'Model1 / Test4': [ u'Model1 / Test4': [
(12, 'equals'), (12, u'equals'),
(13, 'not equal'), (13, u'not equal'),
(14, 'contains'), (14, u'contains'),
(15, 'not contains') (15, u'not contains')
], ],
'Model1 / Bool Field': [ u'Model1 / Bool Field': [
(16, 'equals'), (16, u'equals'),
(17, 'not equal'), (17, u'not equal'),
], ],
'Model1 / Enum Field': [ u'Model1 / Enum Field': [
(18, u'equals'), (18, u'equals'),
(19, u'not equal'), (19, u'not equal'),
] ]})
})
# Test filter with a dot # Test filter with a dot
view = CustomModelView(Model2, db.session, view = CustomModelView(Model2, db.session,
...@@ -280,8 +283,8 @@ def test_column_filters(): ...@@ -280,8 +283,8 @@ def test_column_filters():
'Model1 / Bool Field': [ 'Model1 / Bool Field': [
(0, 'equals'), (0, 'equals'),
(1, 'not equal'), (1, 'not equal'),
], ]})
})
# Fill DB # Fill DB
model1_obj1 = Model1('model1_obj1', bool_field=True) model1_obj1 = Model1('model1_obj1', bool_field=True)
model1_obj2 = Model1('model1_obj2') model1_obj2 = Model1('model1_obj2')
...@@ -302,13 +305,15 @@ def test_column_filters(): ...@@ -302,13 +305,15 @@ def test_column_filters():
rv = client.get('/admin/model1view/?flt0_0=model1_obj1') rv = client.get('/admin/model1view/?flt0_0=model1_obj1')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
ok_('model1_obj1' in rv.data) data = rv.data.decode('utf-8')
ok_('model1_obj2' not in rv.data) ok_('model1_obj1' in data)
ok_('model1_obj2' not in data)
rv = client.get('/admin/model1view/?flt0_5=model1_obj1') rv = client.get('/admin/model1view/?flt0_5=model1_obj1')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
ok_('model1_obj1' in rv.data) data = rv.data.decode('utf-8')
ok_('model1_obj2' in rv.data) ok_('model1_obj1' in data)
ok_('model1_obj2' in data)
# Test different filter types # Test different filter types
view = CustomModelView(Model2, db.session, view = CustomModelView(Model2, db.session,
...@@ -334,10 +339,11 @@ def test_column_filters(): ...@@ -334,10 +339,11 @@ def test_column_filters():
rv = client.get('/admin/_model2/?flt1_0=1') rv = client.get('/admin/_model2/?flt1_0=1')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
ok_('model2_obj1' in rv.data) data = rv.data.decode('utf-8')
ok_('model2_obj2' in rv.data) ok_('model2_obj1' in data)
ok_('model2_obj3' not in rv.data) ok_('model2_obj2' in data)
ok_('model2_obj4' not in rv.data) ok_('model2_obj3' not in data)
ok_('model2_obj4' not in data)
def test_url_args(): def test_url_args():
...@@ -360,35 +366,42 @@ def test_url_args(): ...@@ -360,35 +366,42 @@ def test_url_args():
client = app.test_client() client = app.test_client()
rv = client.get('/admin/model1view/') rv = client.get('/admin/model1view/')
ok_('data1' in rv.data) data = rv.data.decode('utf-8')
ok_('data3' not in rv.data) ok_('data1' in data)
ok_('data3' not in data)
# page # page
rv = client.get('/admin/model1view/?page=1') rv = client.get('/admin/model1view/?page=1')
ok_('data1' not in rv.data) data = rv.data.decode('utf-8')
ok_('data3' in rv.data) ok_('data1' not in data)
ok_('data3' in data)
# sort # sort
rv = client.get('/admin/model1view/?sort=0&desc=1') rv = client.get('/admin/model1view/?sort=0&desc=1')
ok_('data1' not in rv.data) data = rv.data.decode('utf-8')
ok_('data3' in rv.data) ok_('data1' not in data)
ok_('data4' in rv.data) ok_('data3' in data)
ok_('data4' in data)
# search # search
rv = client.get('/admin/model1view/?search=data1') rv = client.get('/admin/model1view/?search=data1')
ok_('data1' in rv.data) data = rv.data.decode('utf-8')
ok_('data2' not in rv.data) ok_('data1' in data)
ok_('data2' not in data)
rv = client.get('/admin/model1view/?search=^data1') rv = client.get('/admin/model1view/?search=^data1')
ok_('data2' not in rv.data) data = rv.data.decode('utf-8')
ok_('data2' not in data)
# like # like
rv = client.get('/admin/model1view/?flt0=0&flt0v=data1') rv = client.get('/admin/model1view/?flt0=0&flt0v=data1')
ok_('data1' in rv.data) data = rv.data.decode('utf-8')
ok_('data1' in data)
# not like # not like
rv = client.get('/admin/model1view/?flt0=1&flt0v=data1') rv = client.get('/admin/model1view/?flt0=1&flt0v=data1')
ok_('data2' in rv.data) data = rv.data.decode('utf-8')
ok_('data2' in data)
def test_non_int_pk(): def test_non_int_pk():
...@@ -414,11 +427,13 @@ def test_non_int_pk(): ...@@ -414,11 +427,13 @@ def test_non_int_pk():
rv = client.get('/admin/modelview/') rv = client.get('/admin/modelview/')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
ok_('test1' in rv.data) data = rv.data.decode('utf-8')
ok_('test1' in data)
rv = client.get('/admin/modelview/edit/?id=test1') rv = client.get('/admin/modelview/edit/?id=test1')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
ok_('test2' in rv.data) data = rv.data.decode('utf-8')
ok_('test2' in data)
def test_form(): def test_form():
...@@ -439,12 +454,12 @@ def test_form_override(): ...@@ -439,12 +454,12 @@ def test_form_override():
db.create_all() db.create_all()
view1 = CustomModelView(Model, db.session, endpoint='view1') view1 = CustomModelView(Model, db.session, endpoint='view1')
view2 = CustomModelView(Model, db.session, endpoint='view2', form_overrides=dict(test=wtf.FileField)) view2 = CustomModelView(Model, db.session, endpoint='view2', form_overrides=dict(test=fields.FileField))
admin.add_view(view1) admin.add_view(view1)
admin.add_view(view2) admin.add_view(view2)
eq_(view1._create_form_class.test.field_class, wtf.TextField) eq_(view1._create_form_class.test.field_class, fields.TextField)
eq_(view2._create_form_class.test.field_class, wtf.FileField) eq_(view2._create_form_class.test.field_class, fields.FileField)
def test_relations(): def test_relations():
...@@ -470,7 +485,7 @@ def test_on_model_change_delete(): ...@@ -470,7 +485,7 @@ def test_on_model_change_delete():
client = app.test_client() client = app.test_client()
client.post('/admin/model1view/new/', client.post('/admin/model1view/new/',
data=dict(test1='test1large', test2='test2')) data=dict(test1='test1large', test2='test2'))
model = db.session.query(Model1).first() model = db.session.query(Model1).first()
eq_(model.test1, 'TEST1LARGE') eq_(model.test1, 'TEST1LARGE')
...@@ -499,7 +514,7 @@ def test_multiple_delete(): ...@@ -499,7 +514,7 @@ def test_multiple_delete():
client = app.test_client() client = app.test_client()
rv = client.post('/admin/model1view/action/', data=dict(action='delete', rowid=[1,2,3])) rv = client.post('/admin/model1view/action/', data=dict(action='delete', rowid=[1, 2, 3]))
eq_(rv.status_code, 302) eq_(rv.status_code, 302)
eq_(M1.query.count(), 0) eq_(M1.query.count(), 0)
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from nose.tools import eq_, ok_, raises from nose.tools import eq_, ok_, raises
from flask.ext import wtf from wtforms import fields
from flask.ext.admin.contrib.sqlamodel import ModelView, fields
from flask.ext.admin.contrib.sqlamodel import ModelView
from flask.ext.admin.contrib.sqlamodel.fields import InlineModelFormList
from . import setup from . import setup
def test_inline_form(): def test_inline_form():
app, db, admin = setup() app, db, admin = setup()
client = app.test_client() client = app.test_client()
...@@ -42,8 +45,8 @@ def test_inline_form(): ...@@ -42,8 +45,8 @@ def test_inline_form():
eq_(view.endpoint, 'userview') eq_(view.endpoint, 'userview')
# Verify form # Verify form
eq_(view._create_form_class.name.field_class, wtf.TextField) eq_(view._create_form_class.name.field_class, fields.TextField)
eq_(view._create_form_class.info.field_class, fields.InlineModelFormList) eq_(view._create_form_class.info.field_class, InlineModelFormList)
rv = client.get('/admin/userview/') rv = client.get('/admin/userview/')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
......
{{ request.method }} - {{ name }} {{ request.method }} - {{ name }}
\ No newline at end of file \ No newline at end of file
...@@ -30,6 +30,7 @@ class MockView(base.BaseView): ...@@ -30,6 +30,7 @@ class MockView(base.BaseView):
else: else:
return False return False
class MockMethodView(base.BaseView): class MockMethodView(base.BaseView):
@base.expose('/') @base.expose('/')
def index(self): def index(self):
...@@ -39,10 +40,13 @@ class MockMethodView(base.BaseView): ...@@ -39,10 +40,13 @@ class MockMethodView(base.BaseView):
class API1(MethodView): class API1(MethodView):
def get(self, cls): def get(self, cls):
return cls.render('method.html', request=request, name='API1') return cls.render('method.html', request=request, name='API1')
def post(self, cls): def post(self, cls):
return cls.render('method.html', request=request, name='API1') return cls.render('method.html', request=request, name='API1')
def put(self, cls): def put(self, cls):
return cls.render('method.html', request=request, name='API1') return cls.render('method.html', request=request, name='API1')
def delete(self, cls): def delete(self, cls):
return cls.render('method.html', request=request, name='API1') return cls.render('method.html', request=request, name='API1')
...@@ -50,9 +54,11 @@ class MockMethodView(base.BaseView): ...@@ -50,9 +54,11 @@ class MockMethodView(base.BaseView):
class API2(MethodView): class API2(MethodView):
def get(self, cls): def get(self, cls):
return cls.render('method.html', request=request, name='API2') return cls.render('method.html', request=request, name='API2')
def post(self, cls): def post(self, cls):
return cls.render('method.html', request=request, name='API2') return cls.render('method.html', request=request, name='API2')
def test_baseview_defaults(): def test_baseview_defaults():
view = MockView() view = MockView()
eq_(view.name, None) eq_(view.name, None)
...@@ -151,11 +157,11 @@ def test_baseview_registration(): ...@@ -151,11 +157,11 @@ def test_baseview_registration():
view = MockView(url='/test/test') view = MockView(url='/test/test')
view.create_blueprint(base.Admin()) view.create_blueprint(base.Admin())
eq_(view.url, '/test/test') eq_(view.url, '/test/test')
view = MockView(endpoint='test') view = MockView(endpoint='test')
view.create_blueprint(base.Admin(url='/')) view.create_blueprint(base.Admin(url='/'))
eq_(view.url, '/test') eq_(view.url, '/test')
view = MockView(static_url_path='/static/my/test') view = MockView(static_url_path='/static/my/test')
view.create_blueprint(base.Admin()) view.create_blueprint(base.Admin())
eq_(view.blueprint.static_url_path, '/static/my/test') eq_(view.blueprint.static_url_path, '/static/my/test')
...@@ -189,15 +195,15 @@ def test_call(): ...@@ -189,15 +195,15 @@ def test_call():
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
rv = client.get('/admin/mockview/') rv = client.get('/admin/mockview/')
eq_(rv.data, 'Success!') eq_(rv.data, b'Success!')
rv = client.get('/admin/mockview/test/') rv = client.get('/admin/mockview/test/')
eq_(rv.data, 'Success!') eq_(rv.data, b'Success!')
# Check authentication failure # Check authentication failure
view.allow_call = False view.allow_call = False
rv = client.get('/admin/mockview/') rv = client.get('/admin/mockview/')
eq_(rv.data, 'Failure!') eq_(rv.data, b'Failure!')
def test_permissions(): def test_permissions():
...@@ -244,16 +250,17 @@ def test_delayed_init(): ...@@ -244,16 +250,17 @@ def test_delayed_init():
client = app.test_client() client = app.test_client()
rv = client.get('/admin/mockview/') rv = client.get('/admin/mockview/')
eq_(rv.data, 'Success!') eq_(rv.data, b'Success!')
def test_multi_instances_init(): def test_multi_instances_init():
app = Flask(__name__) app = Flask(__name__)
admin = base.Admin(app) _ = base.Admin(app)
class ManageIndex(base.AdminIndexView): class ManageIndex(base.AdminIndexView):
pass pass
manage = base.Admin(app, index_view=ManageIndex(url='/manage', endpoint='manage'))
_ = base.Admin(app, index_view=ManageIndex(url='/manage', endpoint='manage'))
@raises(Exception) @raises(Exception)
...@@ -262,6 +269,7 @@ def test_double_init(): ...@@ -262,6 +269,7 @@ def test_double_init():
admin = base.Admin(app) admin = base.Admin(app)
admin.init_app(app) admin.init_app(app)
def test_nested_flask_views(): def test_nested_flask_views():
app = Flask(__name__) app = Flask(__name__)
admin = base.Admin(app) admin = base.Admin(app)
...@@ -272,19 +280,20 @@ def test_nested_flask_views(): ...@@ -272,19 +280,20 @@ def test_nested_flask_views():
client = app.test_client() client = app.test_client()
rv = client.get('/admin/mockmethodview/_api/1') rv = client.get('/admin/mockmethodview/_api/1')
assert rv.data == 'GET - API1' print('"', rv.data, '"')
eq_(rv.data, b'GET - API1')
rv = client.put('/admin/mockmethodview/_api/1') rv = client.put('/admin/mockmethodview/_api/1')
assert rv.data == 'PUT - API1' eq_(rv.data, b'PUT - API1')
rv = client.post('/admin/mockmethodview/_api/1') rv = client.post('/admin/mockmethodview/_api/1')
assert rv.data == 'POST - API1' eq_(rv.data, b'POST - API1')
rv = client.delete('/admin/mockmethodview/_api/1') rv = client.delete('/admin/mockmethodview/_api/1')
assert rv.data == 'DELETE - API1' eq_(rv.data, b'DELETE - API1')
rv = client.get('/admin/mockmethodview/_api/2') rv = client.get('/admin/mockmethodview/_api/2')
assert rv.data == 'GET - API2' eq_(rv.data, b'GET - API2')
rv = client.post('/admin/mockmethodview/_api/2') rv = client.post('/admin/mockmethodview/_api/2')
assert rv.data == 'POST - API2' eq_(rv.data, b'POST - API2')
rv = client.delete('/admin/mockmethodview/_api/2') rv = client.delete('/admin/mockmethodview/_api/2')
assert rv.status_code == 405 eq_(rv.status_code, 405)
rv = client.put('/admin/mockmethodview/_api/2') rv = client.put('/admin/mockmethodview/_api/2')
assert rv.status_code == 405 eq_(rv.status_code, 405)
from nose.tools import eq_, ok_, raises from nose.tools import eq_, ok_
from flask import Flask from flask import Flask
from flask.helpers import get_flashed_messages
from flask.ext.admin import Admin from wtforms import fields
from flask.ext.admin.model import base, filters
from flask.ext import wtf from flask.ext.admin import Admin, form
from flask.ext.admin._compat import iteritems, itervalues
from flask.ext.admin.model import base, filters
class Model(object): class Model(object):
...@@ -17,10 +17,10 @@ class Model(object): ...@@ -17,10 +17,10 @@ class Model(object):
self.col3 = c3 self.col3 = c3
class Form(wtf.Form): class Form(form.BaseForm):
col1 = wtf.TextField() col1 = fields.TextField()
col2 = wtf.TextField() col2 = fields.TextField()
col3 = wtf.TextField() col3 = fields.TextField()
class SimpleFilter(filters.BaseFilter): class SimpleFilter(filters.BaseFilter):
...@@ -36,7 +36,7 @@ class MockModelView(base.BaseModelView): ...@@ -36,7 +36,7 @@ class MockModelView(base.BaseModelView):
def __init__(self, model, name=None, category=None, endpoint=None, url=None, def __init__(self, model, name=None, category=None, endpoint=None, url=None,
**kwargs): **kwargs):
# Allow to set any attributes from parameters # Allow to set any attributes from parameters
for k, v in kwargs.iteritems(): for k, v in iteritems(kwargs):
setattr(self, k, v) setattr(self, k, v)
super(MockModelView, self).__init__(model, name, category, endpoint, url) super(MockModelView, self).__init__(model, name, category, endpoint, url)
...@@ -78,7 +78,7 @@ class MockModelView(base.BaseModelView): ...@@ -78,7 +78,7 @@ class MockModelView(base.BaseModelView):
# Data # Data
def get_list(self, page, sort_field, sort_desc, search, filters): def get_list(self, page, sort_field, sort_desc, search, filters):
self.search_arguments.append((page, sort_field, sort_desc, search, filters)) self.search_arguments.append((page, sort_field, sort_desc, search, filters))
return len(self.all_models), self.all_models.itervalues() return len(self.all_models), itervalues(self.all_models)
def get_one(self, id): def get_one(self, id):
return self.all_models.get(int(id)) return self.all_models.get(int(id))
...@@ -154,7 +154,8 @@ def test_mockview(): ...@@ -154,7 +154,8 @@ def test_mockview():
# Try model edit view # Try model edit view
rv = client.get('/admin/modelview/edit/?id=3') rv = client.get('/admin/modelview/edit/?id=3')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
ok_('test1' in rv.data) data = rv.data.decode('utf-8')
ok_('test1' in data)
rv = client.post('/admin/modelview/edit/?id=3', rv = client.post('/admin/modelview/edit/?id=3',
data=dict(col1='test!', col2='test@', col3='test#')) data=dict(col1='test!', col2='test@', col3='test#'))
...@@ -209,13 +210,13 @@ def test_templates(): ...@@ -209,13 +210,13 @@ def test_templates():
view.edit_template = 'mock.html' view.edit_template = 'mock.html'
rv = client.get('/admin/modelview/') rv = client.get('/admin/modelview/')
eq_(rv.data, 'Success!') eq_(rv.data, b'Success!')
rv = client.get('/admin/modelview/new/') rv = client.get('/admin/modelview/new/')
eq_(rv.data, 'Success!') eq_(rv.data, b'Success!')
rv = client.get('/admin/modelview/edit/?id=1') rv = client.get('/admin/modelview/edit/?id=1')
eq_(rv.data, 'Success!') eq_(rv.data, b'Success!')
def test_list_columns(): def test_list_columns():
...@@ -232,8 +233,9 @@ def test_list_columns(): ...@@ -232,8 +233,9 @@ def test_list_columns():
client = app.test_client() client = app.test_client()
rv = client.get('/admin/modelview/') rv = client.get('/admin/modelview/')
ok_('Column1' in rv.data) data = rv.data.decode('utf-8')
ok_('Col2' not in rv.data) ok_('Column1' in data)
ok_('Col2' not in data)
def test_exclude_columns(): def test_exclude_columns():
...@@ -247,8 +249,9 @@ def test_exclude_columns(): ...@@ -247,8 +249,9 @@ def test_exclude_columns():
client = app.test_client() client = app.test_client()
rv = client.get('/admin/modelview/') rv = client.get('/admin/modelview/')
ok_('Col1' in rv.data) data = rv.data.decode('utf-8')
ok_('Col2' not in rv.data) ok_('Col1' in data)
ok_('Col2' not in data)
def test_sortable_columns(): def test_sortable_columns():
...@@ -298,7 +301,7 @@ def test_form(): ...@@ -298,7 +301,7 @@ def test_form():
def test_custom_form(): def test_custom_form():
app, admin = setup() app, admin = setup()
class TestForm(wtf.Form): class TestForm(form.BaseForm):
pass pass
view = MockModelView(Model, form=TestForm) view = MockModelView(Model, form=TestForm)
......
...@@ -2,6 +2,9 @@ import sys ...@@ -2,6 +2,9 @@ import sys
import warnings import warnings
import traceback import traceback
# Python 3 compatibility
from ._compat import reduce
def import_module(name, required=True): def import_module(name, required=True):
""" """
......
...@@ -43,7 +43,7 @@ setup( ...@@ -43,7 +43,7 @@ setup(
platforms='any', platforms='any',
install_requires=[ install_requires=[
'Flask>=0.7', 'Flask>=0.7',
'Flask-WTF>=0.6' 'wtforms'
], ],
tests_require=[ tests_require=[
'nose>=1.0' 'nose>=1.0'
......
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