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