2699 lines
89 KiB
Python
2699 lines
89 KiB
Python
import pytest
|
|
|
|
from wtforms import fields, validators
|
|
|
|
from flask_admin import form
|
|
from flask_admin.form.fields import Select2Field, DateTimeField
|
|
from flask_admin._compat import as_unicode
|
|
from flask_admin._compat import iteritems
|
|
from flask_admin.contrib.sqla import ModelView, filters, tools
|
|
from flask_babelex import Babel
|
|
|
|
from sqlalchemy.ext.hybrid import hybrid_property
|
|
from sqlalchemy import cast
|
|
from sqlalchemy_utils import EmailType, ChoiceType, UUIDType, URLType, CurrencyType, ColorType, ArrowType, IPAddressType
|
|
from . import setup
|
|
|
|
from datetime import datetime, time, date
|
|
import uuid
|
|
import enum
|
|
import arrow
|
|
|
|
|
|
class CustomModelView(ModelView):
|
|
def __init__(self, model, session,
|
|
name=None, category=None, endpoint=None, url=None,
|
|
**kwargs):
|
|
for k, v in iteritems(kwargs):
|
|
setattr(self, k, v)
|
|
|
|
super(CustomModelView, self).__init__(model, session, name, category,
|
|
endpoint, url)
|
|
form_choices = {
|
|
'choice_field': [
|
|
('choice-1', 'One'),
|
|
('choice-2', 'Two')
|
|
]
|
|
}
|
|
|
|
|
|
def create_models(db):
|
|
class Model1(db.Model):
|
|
def __init__(self, test1=None, test2=None, test3=None, test4=None,
|
|
bool_field=False, date_field=None, time_field=None,
|
|
datetime_field=None, email_field=None,
|
|
choice_field=None, enum_field=None):
|
|
self.test1 = test1
|
|
self.test2 = test2
|
|
self.test3 = test3
|
|
self.test4 = test4
|
|
self.bool_field = bool_field
|
|
self.date_field = date_field
|
|
self.time_field = time_field
|
|
self.datetime_field = datetime_field
|
|
self.email_field = email_field
|
|
self.choice_field = choice_field
|
|
self.enum_field = enum_field
|
|
|
|
class EnumChoices(enum.Enum):
|
|
first = 1
|
|
second = 2
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
test1 = db.Column(db.String(20))
|
|
test2 = db.Column(db.Unicode(20))
|
|
test3 = db.Column(db.Text)
|
|
test4 = db.Column(db.UnicodeText)
|
|
bool_field = db.Column(db.Boolean)
|
|
date_field = db.Column(db.Date)
|
|
time_field = db.Column(db.Time)
|
|
datetime_field = db.Column(db.DateTime)
|
|
email_field = db.Column(EmailType)
|
|
enum_field = db.Column(db.Enum('model1_v1', 'model1_v2'), nullable=True)
|
|
choice_field = db.Column(db.String, nullable=True)
|
|
sqla_utils_choice = db.Column(ChoiceType([
|
|
('choice-1', u'First choice'),
|
|
('choice-2', u'Second choice')
|
|
]))
|
|
sqla_utils_enum = db.Column(ChoiceType(EnumChoices, impl=db.Integer()))
|
|
sqla_utils_arrow = db.Column(ArrowType, default=arrow.utcnow())
|
|
sqla_utils_uuid = db.Column(UUIDType(binary=False), default=uuid.uuid4)
|
|
sqla_utils_url = db.Column(URLType)
|
|
sqla_utils_ip_address = db.Column(IPAddressType)
|
|
sqla_utils_currency = db.Column(CurrencyType)
|
|
sqla_utils_color = db.Column(ColorType)
|
|
|
|
def __unicode__(self):
|
|
return self.test1
|
|
|
|
def __str__(self):
|
|
return self.test1
|
|
|
|
class Model2(db.Model):
|
|
def __init__(self, string_field=None, int_field=None, bool_field=None,
|
|
model1=None, float_field=None, string_field_default=None,
|
|
string_field_empty_default=None):
|
|
self.string_field = string_field
|
|
self.int_field = int_field
|
|
self.bool_field = bool_field
|
|
self.model1 = model1
|
|
self.float_field = float_field
|
|
self.string_field_default = string_field_default
|
|
self.string_field_empty_default = string_field_empty_default
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
string_field = db.Column(db.String)
|
|
string_field_default = db.Column(db.Text, nullable=False,
|
|
default='')
|
|
string_field_empty_default = db.Column(db.Text, nullable=False,
|
|
default='')
|
|
int_field = db.Column(db.Integer)
|
|
bool_field = db.Column(db.Boolean)
|
|
enum_field = db.Column(db.Enum('model2_v1', 'model2_v2'), nullable=True)
|
|
float_field = db.Column(db.Float)
|
|
|
|
# Relation
|
|
model1_id = db.Column(db.Integer, db.ForeignKey(Model1.id))
|
|
model1 = db.relationship(lambda: Model1, backref='model2')
|
|
|
|
db.create_all()
|
|
|
|
return Model1, Model2
|
|
|
|
|
|
def fill_db(db, Model1, Model2):
|
|
model1_obj1 = Model1('test1_val_1', 'test2_val_1', bool_field=True)
|
|
model1_obj2 = Model1('test1_val_2', 'test2_val_2', bool_field=False)
|
|
model1_obj3 = Model1('test1_val_3', 'test2_val_3')
|
|
model1_obj4 = Model1('test1_val_4', 'test2_val_4', email_field="test@test.com", choice_field="choice-1")
|
|
|
|
model2_obj1 = Model2('test2_val_1', model1=model1_obj1, float_field=None)
|
|
model2_obj2 = Model2('test2_val_2', model1=model1_obj2, float_field=None)
|
|
model2_obj3 = Model2('test2_val_3', int_field=5000, float_field=25.9)
|
|
model2_obj4 = Model2('test2_val_4', int_field=9000, float_field=75.5)
|
|
model2_obj5 = Model2('test2_val_5', int_field=6169453081680413441)
|
|
|
|
date_obj1 = Model1('date_obj1', date_field=date(2014, 11, 17))
|
|
date_obj2 = Model1('date_obj2', date_field=date(2013, 10, 16))
|
|
timeonly_obj1 = Model1('timeonly_obj1', time_field=time(11, 10, 9))
|
|
timeonly_obj2 = Model1('timeonly_obj2', time_field=time(10, 9, 8))
|
|
datetime_obj1 = Model1('datetime_obj1', datetime_field=datetime(2014, 4, 3, 1, 9, 0))
|
|
datetime_obj2 = Model1('datetime_obj2', datetime_field=datetime(2013, 3, 2, 0, 8, 0))
|
|
|
|
enum_obj1 = Model1('enum_obj1', enum_field="model1_v1")
|
|
enum_obj2 = Model1('enum_obj2', enum_field="model1_v2")
|
|
|
|
empty_obj = Model1(test2="empty_obj")
|
|
|
|
db.session.add_all([
|
|
model1_obj1, model1_obj2, model1_obj3, model1_obj4,
|
|
model2_obj1, model2_obj2, model2_obj3, model2_obj4, model2_obj5,
|
|
date_obj1, timeonly_obj1, datetime_obj1,
|
|
date_obj2, timeonly_obj2, datetime_obj2,
|
|
enum_obj1, enum_obj2, empty_obj
|
|
])
|
|
db.session.commit()
|
|
|
|
|
|
def test_model():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
view = CustomModelView(Model1, db.session)
|
|
|
|
admin.add_view(view)
|
|
|
|
assert view.model == Model1
|
|
assert view.name == 'Model1'
|
|
assert view.endpoint == 'model1'
|
|
|
|
assert view._primary_key == 'id'
|
|
|
|
assert 'test1' in view._sortable_columns
|
|
assert 'test2' in view._sortable_columns
|
|
assert 'test3' in view._sortable_columns
|
|
assert 'test4' in view._sortable_columns
|
|
|
|
assert view._create_form_class is not None
|
|
assert view._edit_form_class is not None
|
|
assert not view._search_supported
|
|
assert view._filters is None
|
|
|
|
# Verify form
|
|
assert view._create_form_class.test1.field_class == fields.StringField
|
|
assert view._create_form_class.test2.field_class == fields.StringField
|
|
assert view._create_form_class.test3.field_class == fields.TextAreaField
|
|
assert view._create_form_class.test4.field_class == fields.TextAreaField
|
|
assert view._create_form_class.email_field.field_class == fields.StringField
|
|
assert view._create_form_class.choice_field.field_class == Select2Field
|
|
assert view._create_form_class.enum_field.field_class == Select2Field
|
|
assert view._create_form_class.sqla_utils_choice.field_class == Select2Field
|
|
assert view._create_form_class.sqla_utils_enum.field_class == Select2Field
|
|
assert view._create_form_class.sqla_utils_arrow.field_class == DateTimeField
|
|
assert view._create_form_class.sqla_utils_uuid.field_class == fields.StringField
|
|
assert view._create_form_class.sqla_utils_url.field_class == fields.StringField
|
|
assert view._create_form_class.sqla_utils_ip_address.field_class == fields.StringField
|
|
assert view._create_form_class.sqla_utils_currency.field_class == fields.StringField
|
|
assert view._create_form_class.sqla_utils_color.field_class == fields.StringField
|
|
|
|
# Make some test clients
|
|
client = app.test_client()
|
|
|
|
# check that we can retrieve a list view
|
|
rv = client.get('/admin/model1/')
|
|
assert rv.status_code == 200
|
|
|
|
# check that we can retrieve a 'create' view
|
|
rv = client.get('/admin/model1/new/')
|
|
assert rv.status_code == 200
|
|
|
|
# create a new record
|
|
uuid_obj = uuid.uuid4()
|
|
rv = client.post(
|
|
'/admin/model1/new/',
|
|
data=dict(
|
|
test1='test1large',
|
|
test2='test2',
|
|
time_field=time(0, 0, 0),
|
|
email_field="Test@TEST.com",
|
|
choice_field="choice-1",
|
|
enum_field='model1_v1',
|
|
sqla_utils_choice="choice-1",
|
|
sqla_utils_enum=1,
|
|
sqla_utils_arrow='2018-10-27 14:17:00',
|
|
sqla_utils_uuid=str(uuid_obj),
|
|
sqla_utils_url="http://www.example.com",
|
|
sqla_utils_ip_address='127.0.0.1',
|
|
sqla_utils_currency='USD',
|
|
sqla_utils_color='#f0f0f0',
|
|
)
|
|
)
|
|
assert rv.status_code == 302
|
|
|
|
# check that the new record was persisted
|
|
model = db.session.query(Model1).first()
|
|
assert model.test1 == u'test1large'
|
|
assert model.test2 == u'test2'
|
|
assert model.test3 == u''
|
|
assert model.test4 == u''
|
|
assert model.email_field == u'test@test.com'
|
|
assert model.choice_field == u'choice-1'
|
|
assert model.enum_field == u'model1_v1'
|
|
assert model.sqla_utils_choice == u'choice-1'
|
|
assert model.sqla_utils_enum.value == 1
|
|
assert model.sqla_utils_arrow == arrow.get('2018-10-27 14:17:00')
|
|
assert model.sqla_utils_uuid == uuid_obj
|
|
assert model.sqla_utils_url == "http://www.example.com"
|
|
assert str(model.sqla_utils_ip_address) == '127.0.0.1'
|
|
assert str(model.sqla_utils_currency) == 'USD'
|
|
assert model.sqla_utils_color.hex == '#f0f0f0'
|
|
|
|
# check that the new record shows up on the list view
|
|
rv = client.get('/admin/model1/')
|
|
assert rv.status_code == 200
|
|
assert u'test1large' in rv.data.decode('utf-8')
|
|
|
|
# check that we can retrieve an edit view
|
|
url = '/admin/model1/edit/?id=%s' % model.id
|
|
rv = client.get(url)
|
|
assert rv.status_code == 200
|
|
|
|
# verify that midnight does not show as blank
|
|
assert u'00:00:00' in rv.data.decode('utf-8')
|
|
|
|
# edit the record
|
|
new_uuid_obj = uuid.uuid4()
|
|
rv = client.post(url,
|
|
data=dict(test1='test1small',
|
|
test2='test2large',
|
|
email_field='Test2@TEST.com',
|
|
choice_field='__None',
|
|
enum_field='__None',
|
|
sqla_utils_choice='__None',
|
|
sqla_utils_enum='__None',
|
|
sqla_utils_arrow='',
|
|
sqla_utils_uuid=str(new_uuid_obj),
|
|
sqla_utils_url='',
|
|
sqla_utils_ip_address='',
|
|
sqla_utils_currency='',
|
|
sqla_utils_color='',
|
|
))
|
|
assert rv.status_code == 302
|
|
|
|
# check that the changes were persisted
|
|
model = db.session.query(Model1).first()
|
|
assert model.test1 == 'test1small'
|
|
assert model.test2 == 'test2large'
|
|
assert model.test3 == ''
|
|
assert model.test4 == ''
|
|
assert model.email_field == u'test2@test.com'
|
|
assert model.choice_field is None
|
|
assert model.enum_field is None
|
|
assert model.sqla_utils_choice is None
|
|
assert model.sqla_utils_enum is None
|
|
assert model.sqla_utils_arrow is None
|
|
assert model.sqla_utils_uuid == new_uuid_obj
|
|
assert model.sqla_utils_url is None
|
|
assert model.sqla_utils_ip_address is None
|
|
assert model.sqla_utils_currency is None
|
|
assert model.sqla_utils_color is None
|
|
|
|
# check that the model can be deleted
|
|
url = '/admin/model1/delete/?id=%s' % model.id
|
|
rv = client.post(url)
|
|
assert rv.status_code == 302
|
|
assert db.session.query(Model1).count() == 0
|
|
|
|
|
|
@pytest.mark.xfail(raises=Exception)
|
|
def test_no_pk():
|
|
app, db, admin = setup()
|
|
|
|
class Model(db.Model):
|
|
test = db.Column(db.Integer)
|
|
|
|
view = CustomModelView(Model)
|
|
admin.add_view(view)
|
|
|
|
|
|
def test_list_columns():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
# test column_list with a list of strings
|
|
view = CustomModelView(Model1, db.session,
|
|
column_list=['test1', 'test3'],
|
|
column_labels=dict(test1='Column1'))
|
|
admin.add_view(view)
|
|
|
|
# test column_list with a list of SQLAlchemy columns
|
|
view2 = CustomModelView(Model1, db.session, endpoint='model1_2',
|
|
column_list=[Model1.test1, Model1.test3],
|
|
column_labels=dict(test1='Column1'))
|
|
admin.add_view(view2)
|
|
|
|
assert len(view._list_columns) == 2
|
|
assert view._list_columns == [('test1', 'Column1'), ('test3', 'Test3')]
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.get('/admin/model1/')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'Column1' in data
|
|
assert 'Test2' not in data
|
|
|
|
assert len(view2._list_columns) == 2
|
|
assert view2._list_columns == [('test1', 'Column1'), ('test3', 'Test3')]
|
|
|
|
rv = client.get('/admin/model1_2/')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'Column1' in data
|
|
assert 'Test2' not in data
|
|
|
|
|
|
def test_complex_list_columns():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
M1, M2 = create_models(db)
|
|
|
|
m1 = M1('model1_val1')
|
|
db.session.add(m1)
|
|
db.session.add(M2('model2_val1', model1=m1))
|
|
|
|
db.session.commit()
|
|
|
|
# test column_list with a list of strings on a relation
|
|
view = CustomModelView(M2, db.session,
|
|
column_list=['model1.test1'])
|
|
admin.add_view(view)
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.get('/admin/model2/')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'model1_val1' in data
|
|
|
|
|
|
def test_exclude_columns():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
view = CustomModelView(
|
|
Model1, db.session,
|
|
column_exclude_list=['test2', 'test4', 'enum_field', 'date_field', 'time_field', 'datetime_field',
|
|
'sqla_utils_choice', 'sqla_utils_enum', 'sqla_utils_arrow', 'sqla_utils_uuid',
|
|
'sqla_utils_url', 'sqla_utils_ip_address', 'sqla_utils_currency', 'sqla_utils_color']
|
|
)
|
|
admin.add_view(view)
|
|
|
|
assert \
|
|
view._list_columns == \
|
|
[('test1', 'Test1'), ('test3', 'Test3'), ('bool_field', 'Bool Field'),
|
|
('email_field', 'Email Field'), ('choice_field', 'Choice Field')]
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.get('/admin/model1/')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'Test1' in data
|
|
assert 'Test2' not in data
|
|
|
|
|
|
def test_column_searchable_list():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
view = CustomModelView(Model2, db.session,
|
|
column_searchable_list=['string_field', 'int_field'])
|
|
admin.add_view(view)
|
|
|
|
assert view._search_supported
|
|
assert len(view._search_fields) == 2
|
|
|
|
assert isinstance(view._search_fields[0][0], db.Column)
|
|
assert isinstance(view._search_fields[1][0], db.Column)
|
|
assert view._search_fields[0][0].name == 'string_field'
|
|
assert view._search_fields[1][0].name == 'int_field'
|
|
|
|
db.session.add(Model2('model1-test', 5000))
|
|
db.session.add(Model2('model2-test', 9000))
|
|
db.session.commit()
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.get('/admin/model2/?search=model1')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'model1-test' in data
|
|
assert 'model2-test' not in data
|
|
|
|
rv = client.get('/admin/model2/?search=9000')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'model1-test' not in data
|
|
assert 'model2-test' in data
|
|
|
|
|
|
def test_extra_args_search():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
view1 = CustomModelView(Model1, db.session,
|
|
column_searchable_list=['test1', ])
|
|
|
|
admin.add_view(view1)
|
|
|
|
db.session.add(Model2('model1-test', ))
|
|
db.session.commit()
|
|
|
|
client = app.test_client()
|
|
|
|
# check that extra args in the url are propagated as hidden fields in the search form
|
|
rv = client.get('/admin/model1/?search=model1&foo=bar')
|
|
data = rv.data.decode('utf-8')
|
|
assert '<input type="hidden" name="foo" value="bar">' in data
|
|
|
|
|
|
def test_extra_args_filter():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
view2 = CustomModelView(Model2, db.session,
|
|
column_filters=['int_field', ])
|
|
admin.add_view(view2)
|
|
|
|
db.session.add(Model2('model2-test', 5000))
|
|
db.session.commit()
|
|
|
|
client = app.test_client()
|
|
|
|
# check that extra args in the url are propagated as hidden fields in the form
|
|
rv = client.get('/admin/model2/?flt1_0=5000&foo=bar')
|
|
data = rv.data.decode('utf-8')
|
|
assert '<input type="hidden" name="foo" value="bar">' in data
|
|
|
|
|
|
def test_complex_searchable_list():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
view = CustomModelView(Model2, db.session,
|
|
column_searchable_list=['model1.test1'])
|
|
admin.add_view(view)
|
|
view2 = CustomModelView(Model1, db.session,
|
|
column_searchable_list=[Model2.string_field])
|
|
admin.add_view(view2)
|
|
|
|
m1 = Model1('model1-test1-val')
|
|
m2 = Model1('model1-test2-val')
|
|
db.session.add(m1)
|
|
db.session.add(m2)
|
|
db.session.add(Model2('model2-test1-val', model1=m1))
|
|
db.session.add(Model2('model2-test2-val', model1=m2))
|
|
db.session.commit()
|
|
|
|
client = app.test_client()
|
|
|
|
# test relation string - 'model1.test1'
|
|
rv = client.get('/admin/model2/?search=model1-test1')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'model2-test1-val' in data
|
|
assert 'model2-test2-val' not in data
|
|
|
|
# test relation object - Model2.string_field
|
|
rv = client.get('/admin/model1/?search=model2-test1')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'model1-test1-val' in data
|
|
assert 'model1-test2-val' not in data
|
|
|
|
|
|
def test_complex_searchable_list_missing_children():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
view = CustomModelView(Model1, db.session,
|
|
column_searchable_list=[
|
|
'test1', 'model2.string_field'])
|
|
admin.add_view(view)
|
|
|
|
db.session.add(Model1('magic string'))
|
|
db.session.commit()
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.get('/admin/model1/?search=magic')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'magic string' in data
|
|
|
|
|
|
def test_column_editable_list():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
view = CustomModelView(Model1, db.session,
|
|
column_editable_list=['test1', 'enum_field'])
|
|
admin.add_view(view)
|
|
# Test in-line editing for relations
|
|
view = CustomModelView(Model2, db.session, column_editable_list=['model1'])
|
|
admin.add_view(view)
|
|
|
|
fill_db(db, Model1, Model2)
|
|
|
|
client = app.test_client()
|
|
|
|
# Test in-line edit field rendering
|
|
rv = client.get('/admin/model1/')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'data-role="x-editable"' in data
|
|
|
|
# Form - Test basic in-line edit functionality
|
|
rv = client.post('/admin/model1/ajax/update/', data={
|
|
'list_form_pk': '1',
|
|
'test1': 'change-success-1',
|
|
})
|
|
data = rv.data.decode('utf-8')
|
|
assert 'Record was successfully saved.' == data
|
|
|
|
# ensure the value has changed
|
|
rv = client.get('/admin/model1/')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'change-success-1' in data
|
|
|
|
# Test validation error
|
|
rv = client.post('/admin/model1/ajax/update/', data={
|
|
'list_form_pk': '1',
|
|
'enum_field': 'problematic-input',
|
|
})
|
|
assert rv.status_code == 500
|
|
|
|
# Test invalid primary key
|
|
rv = client.post('/admin/model1/ajax/update/', data={
|
|
'list_form_pk': '1000',
|
|
'test1': 'problematic-input',
|
|
})
|
|
data = rv.data.decode('utf-8')
|
|
assert rv.status_code == 500
|
|
|
|
# Test editing column not in column_editable_list
|
|
rv = client.post('/admin/model1/ajax/update/', data={
|
|
'list_form_pk': '1',
|
|
'test2': 'problematic-input',
|
|
})
|
|
data = rv.data.decode('utf-8')
|
|
assert 'problematic-input' not in data
|
|
|
|
rv = client.post('/admin/model2/ajax/update/', data={
|
|
'list_form_pk': '1',
|
|
'model1': '3',
|
|
})
|
|
data = rv.data.decode('utf-8')
|
|
assert 'Record was successfully saved.' == data
|
|
|
|
# confirm the value has changed
|
|
rv = client.get('/admin/model2/')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test1_val_3' in data
|
|
|
|
|
|
def test_details_view():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
view_no_details = CustomModelView(Model1, db.session)
|
|
admin.add_view(view_no_details)
|
|
|
|
# fields are scaffolded
|
|
view_w_details = CustomModelView(Model2, db.session, can_view_details=True)
|
|
admin.add_view(view_w_details)
|
|
|
|
# show only specific fields in details w/ column_details_list
|
|
string_field_view = CustomModelView(Model2, db.session,
|
|
can_view_details=True,
|
|
column_details_list=["string_field"],
|
|
endpoint="sf_view")
|
|
admin.add_view(string_field_view)
|
|
|
|
fill_db(db, Model1, Model2)
|
|
|
|
client = app.test_client()
|
|
|
|
# ensure link to details is hidden when can_view_details is disabled
|
|
rv = client.get('/admin/model1/')
|
|
data = rv.data.decode('utf-8')
|
|
assert '/admin/model1/details/' not in data
|
|
|
|
# ensure link to details view appears
|
|
rv = client.get('/admin/model2/')
|
|
data = rv.data.decode('utf-8')
|
|
assert '/admin/model2/details/' in data
|
|
|
|
# test redirection when details are disabled
|
|
rv = client.get('/admin/model1/details/?url=%2Fadmin%2Fmodel1%2F&id=1')
|
|
assert rv.status_code == 302
|
|
|
|
# test if correct data appears in details view when enabled
|
|
rv = client.get('/admin/model2/details/?url=%2Fadmin%2Fmodel2%2F&id=1')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'String Field' in data
|
|
assert 'test2_val_1' in data
|
|
assert 'test1_val_1' in data
|
|
|
|
# test column_details_list
|
|
rv = client.get('/admin/sf_view/details/?url=%2Fadmin%2Fsf_view%2F&id=1')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'String Field' in data
|
|
assert 'test2_val_1' in data
|
|
assert 'test1_val_1' not in data
|
|
|
|
|
|
def test_editable_list_special_pks():
|
|
''' Tests editable list view + a primary key with special characters
|
|
'''
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
class Model1(db.Model):
|
|
def __init__(self, id=None, val1=None):
|
|
self.id = id
|
|
self.val1 = val1
|
|
|
|
id = db.Column(db.String(20), primary_key=True)
|
|
val1 = db.Column(db.String(20))
|
|
|
|
db.create_all()
|
|
|
|
view = CustomModelView(Model1, db.session, column_editable_list=['val1'])
|
|
admin.add_view(view)
|
|
|
|
db.session.add(Model1('1-1', 'test1'))
|
|
db.session.add(Model1('1-5', 'test2'))
|
|
db.session.commit()
|
|
|
|
client = app.test_client()
|
|
|
|
# Form - Test basic in-line edit functionality
|
|
rv = client.post('/admin/model1/ajax/update/', data={
|
|
'list_form_pk': '1-1',
|
|
'val1': 'change-success-1',
|
|
})
|
|
data = rv.data.decode('utf-8')
|
|
assert 'Record was successfully saved.' == data
|
|
|
|
# ensure the value has changed
|
|
rv = client.get('/admin/model1/')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'change-success-1' in data
|
|
|
|
|
|
def test_column_filters():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
view1 = CustomModelView(
|
|
Model1, db.session,
|
|
column_filters=['test1']
|
|
)
|
|
admin.add_view(view1)
|
|
|
|
client = app.test_client()
|
|
|
|
assert len(view1._filters) == 7
|
|
|
|
# Generate views
|
|
view2 = CustomModelView(Model2, db.session,
|
|
column_filters=['model1'])
|
|
|
|
view5 = CustomModelView(Model1, db.session,
|
|
column_filters=['test1'], endpoint='_strings')
|
|
admin.add_view(view5)
|
|
|
|
view6 = CustomModelView(Model2, db.session,
|
|
column_filters=['int_field'])
|
|
admin.add_view(view6)
|
|
|
|
view7 = CustomModelView(Model1, db.session, column_filters=['bool_field'],
|
|
endpoint="_bools")
|
|
admin.add_view(view7)
|
|
|
|
view8 = CustomModelView(Model2, db.session, column_filters=['float_field'],
|
|
endpoint="_float")
|
|
admin.add_view(view8)
|
|
|
|
view9 = CustomModelView(
|
|
Model2, db.session,
|
|
endpoint='_model2',
|
|
column_filters=['model1.bool_field'],
|
|
column_list=[
|
|
'string_field',
|
|
'model1.id',
|
|
'model1.bool_field',
|
|
]
|
|
)
|
|
admin.add_view(view9)
|
|
|
|
view10 = CustomModelView(
|
|
Model1, db.session,
|
|
column_filters=['test1'],
|
|
endpoint='_model3',
|
|
named_filter_urls=True
|
|
)
|
|
admin.add_view(view10)
|
|
|
|
view11 = CustomModelView(Model1, db.session,
|
|
column_filters=['date_field', 'datetime_field', 'time_field'],
|
|
endpoint="_datetime")
|
|
admin.add_view(view11)
|
|
|
|
view12 = CustomModelView(Model1, db.session,
|
|
column_filters=['enum_field'],
|
|
endpoint="_enumfield")
|
|
admin.add_view(view12)
|
|
|
|
view13 = CustomModelView(Model2, db.session,
|
|
column_filters=[
|
|
filters.FilterEqual(Model1.test1, "Test1")
|
|
], endpoint='_relation_test')
|
|
admin.add_view(view13)
|
|
|
|
# Test views
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view1._filter_groups[u'Test1']] == \
|
|
[
|
|
(0, u'contains'),
|
|
(1, u'not contains'),
|
|
(2, u'equals'),
|
|
(3, u'not equal'),
|
|
(4, u'empty'),
|
|
(5, u'in list'),
|
|
(6, u'not in list'),
|
|
]
|
|
|
|
# Test filter that references property0
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view2._filter_groups[u'Model1 / Test1']] == \
|
|
[
|
|
(0, u'contains'),
|
|
(1, u'not contains'),
|
|
(2, u'equals'),
|
|
(3, u'not equal'),
|
|
(4, u'empty'),
|
|
(5, u'in list'),
|
|
(6, u'not in list'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view2._filter_groups[u'Model1 / Test2']] == \
|
|
[
|
|
(7, u'contains'),
|
|
(8, u'not contains'),
|
|
(9, u'equals'),
|
|
(10, u'not equal'),
|
|
(11, u'empty'),
|
|
(12, u'in list'),
|
|
(13, u'not in list'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view2._filter_groups[u'Model1 / Test3']] == \
|
|
[
|
|
(14, u'contains'),
|
|
(15, u'not contains'),
|
|
(16, u'equals'),
|
|
(17, u'not equal'),
|
|
(18, u'empty'),
|
|
(19, u'in list'),
|
|
(20, u'not in list'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view2._filter_groups[u'Model1 / Test4']] == \
|
|
[
|
|
(21, u'contains'),
|
|
(22, u'not contains'),
|
|
(23, u'equals'),
|
|
(24, u'not equal'),
|
|
(25, u'empty'),
|
|
(26, u'in list'),
|
|
(27, u'not in list'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view2._filter_groups[u'Model1 / Bool Field']] == \
|
|
[
|
|
(28, u'equals'),
|
|
(29, u'not equal'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view2._filter_groups[u'Model1 / Date Field']] == \
|
|
[
|
|
(30, u'equals'),
|
|
(31, u'not equal'),
|
|
(32, u'greater than'),
|
|
(33, u'smaller than'),
|
|
(34, u'between'),
|
|
(35, u'not between'),
|
|
(36, u'empty'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view2._filter_groups[u'Model1 / Time Field']] == \
|
|
[
|
|
(37, u'equals'),
|
|
(38, u'not equal'),
|
|
(39, u'greater than'),
|
|
(40, u'smaller than'),
|
|
(41, u'between'),
|
|
(42, u'not between'),
|
|
(43, u'empty'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view2._filter_groups[u'Model1 / Datetime Field']] == \
|
|
[
|
|
(44, u'equals'),
|
|
(45, u'not equal'),
|
|
(46, u'greater than'),
|
|
(47, u'smaller than'),
|
|
(48, u'between'),
|
|
(49, u'not between'),
|
|
(50, u'empty'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view2._filter_groups[u'Model1 / Email Field']] == \
|
|
[
|
|
(51, u'contains'),
|
|
(52, u'not contains'),
|
|
(53, u'equals'),
|
|
(54, u'not equal'),
|
|
(55, u'empty'),
|
|
(56, u'in list'),
|
|
(57, u'not in list'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view2._filter_groups[u'Model1 / Enum Field']] == \
|
|
[
|
|
(58, u'equals'),
|
|
(59, u'not equal'),
|
|
(60, u'empty'),
|
|
(61, u'in list'),
|
|
(62, u'not in list'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view2._filter_groups[u'Model1 / Choice Field']] == \
|
|
[
|
|
(63, u'contains'),
|
|
(64, u'not contains'),
|
|
(65, u'equals'),
|
|
(66, u'not equal'),
|
|
(67, u'empty'),
|
|
(68, u'in list'),
|
|
(69, u'not in list'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view2._filter_groups[u'Model1 / Sqla Utils Choice']] == \
|
|
[
|
|
(70, u'equals'),
|
|
(71, u'not equal'),
|
|
(72, u'contains'),
|
|
(73, u'not contains'),
|
|
(74, u'empty'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view2._filter_groups[u'Model1 / Sqla Utils Enum']] == \
|
|
[
|
|
(75, u'equals'),
|
|
(76, u'not equal'),
|
|
(77, u'contains'),
|
|
(78, u'not contains'),
|
|
(79, u'empty'),
|
|
]
|
|
|
|
# Test filter with a dot
|
|
view3 = CustomModelView(Model2, db.session,
|
|
column_filters=['model1.bool_field'])
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view3._filter_groups[u'model1 / Model1 / Bool Field']] == \
|
|
[
|
|
(0, 'equals'),
|
|
(1, 'not equal'),
|
|
]
|
|
|
|
# Test column_labels on filters
|
|
view4 = CustomModelView(Model2, db.session,
|
|
column_filters=['model1.bool_field', 'string_field'],
|
|
column_labels={
|
|
'model1.bool_field': 'Test Filter #1',
|
|
'string_field': 'Test Filter #2',
|
|
})
|
|
|
|
assert list(view4._filter_groups.keys()) == [u'Test Filter #1', u'Test Filter #2']
|
|
|
|
fill_db(db, Model1, Model2)
|
|
|
|
# Test equals
|
|
rv = client.get('/admin/model1/?flt0_0=test1_val_1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
# the filter value is always in "data"
|
|
# need to check a different column than test1 for the expected row
|
|
|
|
assert 'test2_val_1' in data
|
|
assert 'test1_val_2' not in data
|
|
|
|
# Test NOT IN filter
|
|
rv = client.get('/admin/model1/?flt0_6=test1_val_1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
|
|
assert 'test1_val_2' in data
|
|
assert 'test2_val_1' not in data
|
|
|
|
# Test string filter
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view5._filter_groups[u'Test1']] == \
|
|
[
|
|
(0, 'contains'),
|
|
(1, 'not contains'),
|
|
(2, 'equals'),
|
|
(3, 'not equal'),
|
|
(4, 'empty'),
|
|
(5, 'in list'),
|
|
(6, 'not in list'),
|
|
]
|
|
|
|
# string - equals
|
|
rv = client.get('/admin/_strings/?flt0_0=test1_val_1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' in data
|
|
assert 'test1_val_2' not in data
|
|
|
|
# string - not equal
|
|
rv = client.get('/admin/_strings/?flt0_1=test1_val_1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' not in data
|
|
assert 'test1_val_2' in data
|
|
|
|
# string - contains
|
|
rv = client.get('/admin/_strings/?flt0_2=test1_val_1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' in data
|
|
assert 'test1_val_2' not in data
|
|
|
|
# string - not contains
|
|
rv = client.get('/admin/_strings/?flt0_3=test1_val_1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' not in data
|
|
assert 'test1_val_2' in data
|
|
|
|
# string - empty
|
|
rv = client.get('/admin/_strings/?flt0_4=1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'empty_obj' in data
|
|
assert 'test1_val_1' not in data
|
|
assert 'test1_val_2' not in data
|
|
|
|
# string - not empty
|
|
rv = client.get('/admin/_strings/?flt0_4=0')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'empty_obj' not in data
|
|
assert 'test1_val_1' in data
|
|
assert 'test1_val_2' in data
|
|
|
|
# string - in list
|
|
rv = client.get('/admin/_strings/?flt0_5=test1_val_1%2Ctest1_val_2')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' in data
|
|
assert 'test2_val_2' in data
|
|
assert 'test1_val_3' not in data
|
|
assert 'test1_val_4' not in data
|
|
|
|
# string - not in list
|
|
rv = client.get('/admin/_strings/?flt0_6=test1_val_1%2Ctest1_val_2')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' not in data
|
|
assert 'test2_val_2' not in data
|
|
assert 'test1_val_3' in data
|
|
assert 'test1_val_4' in data
|
|
|
|
# Test integer filter
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view6._filter_groups[u'Int Field']] == \
|
|
[
|
|
(0, 'equals'),
|
|
(1, 'not equal'),
|
|
(2, 'greater than'),
|
|
(3, 'smaller than'),
|
|
(4, 'empty'),
|
|
(5, 'in list'),
|
|
(6, 'not in list'),
|
|
]
|
|
|
|
# integer - equals
|
|
rv = client.get('/admin/model2/?flt0_0=5000')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_3' in data
|
|
assert 'test2_val_4' not in data
|
|
|
|
# integer - equals (huge number)
|
|
rv = client.get('/admin/model2/?flt0_0=6169453081680413441')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_5' in data
|
|
assert 'test2_val_4' not in data
|
|
|
|
# integer - equals - test validation
|
|
rv = client.get('/admin/model2/?flt0_0=badval')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'Invalid Filter Value' in data
|
|
|
|
# integer - not equal
|
|
rv = client.get('/admin/model2/?flt0_1=5000')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_3' not in data
|
|
assert 'test2_val_4' in data
|
|
|
|
# integer - greater
|
|
rv = client.get('/admin/model2/?flt0_2=6000')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_3' not in data
|
|
assert 'test2_val_4' in data
|
|
|
|
# integer - smaller
|
|
rv = client.get('/admin/model2/?flt0_3=6000')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_3' in data
|
|
assert 'test2_val_4' not in data
|
|
|
|
# integer - empty
|
|
rv = client.get('/admin/model2/?flt0_4=1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' in data
|
|
assert 'test2_val_2' in data
|
|
assert 'test2_val_3' not in data
|
|
assert 'test2_val_4' not in data
|
|
|
|
# integer - not empty
|
|
rv = client.get('/admin/model2/?flt0_4=0')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' not in data
|
|
assert 'test2_val_2' not in data
|
|
assert 'test2_val_3' in data
|
|
assert 'test2_val_4' in data
|
|
|
|
# integer - in list
|
|
rv = client.get('/admin/model2/?flt0_5=5000%2C9000')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' not in data
|
|
assert 'test2_val_2' not in data
|
|
assert 'test2_val_3' in data
|
|
assert 'test2_val_4' in data
|
|
|
|
# integer - in list (huge number)
|
|
rv = client.get('/admin/model2/?flt0_5=6169453081680413441')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' not in data
|
|
assert 'test2_val_5' in data
|
|
|
|
# integer - in list - test validation
|
|
rv = client.get('/admin/model2/?flt0_5=5000%2Cbadval')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'Invalid Filter Value' in data
|
|
|
|
# integer - not in list
|
|
rv = client.get('/admin/model2/?flt0_6=5000%2C9000')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' in data
|
|
assert 'test2_val_2' in data
|
|
assert 'test2_val_3' not in data
|
|
assert 'test2_val_4' not in data
|
|
|
|
# Test boolean filter
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view7._filter_groups[u'Bool Field']] == \
|
|
[
|
|
(0, 'equals'),
|
|
(1, 'not equal'),
|
|
]
|
|
|
|
# boolean - equals - Yes
|
|
rv = client.get('/admin/_bools/?flt0_0=1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' in data
|
|
assert 'test2_val_2' not in data
|
|
assert 'test2_val_3' not in data
|
|
|
|
# boolean - equals - No
|
|
rv = client.get('/admin/_bools/?flt0_0=0')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' not in data
|
|
assert 'test2_val_2' in data
|
|
assert 'test2_val_3' in data
|
|
|
|
# boolean - not equals - Yes
|
|
rv = client.get('/admin/_bools/?flt0_1=1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' not in data
|
|
assert 'test2_val_2' in data
|
|
assert 'test2_val_3' in data
|
|
|
|
# boolean - not equals - No
|
|
rv = client.get('/admin/_bools/?flt0_1=0')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' in data
|
|
assert 'test2_val_2' not in data
|
|
assert 'test2_val_3' not in data
|
|
|
|
# Test float filter
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view8._filter_groups[u'Float Field']] == \
|
|
[
|
|
(0, 'equals'),
|
|
(1, 'not equal'),
|
|
(2, 'greater than'),
|
|
(3, 'smaller than'),
|
|
(4, 'empty'),
|
|
(5, 'in list'),
|
|
(6, 'not in list'),
|
|
]
|
|
|
|
# float - equals
|
|
rv = client.get('/admin/_float/?flt0_0=25.9')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_3' in data
|
|
assert 'test2_val_4' not in data
|
|
|
|
# float - equals - test validation
|
|
rv = client.get('/admin/_float/?flt0_0=badval')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'Invalid Filter Value' in data
|
|
|
|
# float - not equal
|
|
rv = client.get('/admin/_float/?flt0_1=25.9')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_3' not in data
|
|
assert 'test2_val_4' in data
|
|
|
|
# float - greater
|
|
rv = client.get('/admin/_float/?flt0_2=60.5')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_3' not in data
|
|
assert 'test2_val_4' in data
|
|
|
|
# float - smaller
|
|
rv = client.get('/admin/_float/?flt0_3=60.5')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_3' in data
|
|
assert 'test2_val_4' not in data
|
|
|
|
# float - empty
|
|
rv = client.get('/admin/_float/?flt0_4=1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' in data
|
|
assert 'test2_val_2' in data
|
|
assert 'test2_val_3' not in data
|
|
assert 'test2_val_4' not in data
|
|
|
|
# float - not empty
|
|
rv = client.get('/admin/_float/?flt0_4=0')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' not in data
|
|
assert 'test2_val_2' not in data
|
|
assert 'test2_val_3' in data
|
|
assert 'test2_val_4' in data
|
|
|
|
# float - in list
|
|
rv = client.get('/admin/_float/?flt0_5=25.9%2C75.5')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' not in data
|
|
assert 'test2_val_2' not in data
|
|
assert 'test2_val_3' in data
|
|
assert 'test2_val_4' in data
|
|
|
|
# float - in list - test validation
|
|
rv = client.get('/admin/_float/?flt0_5=25.9%2Cbadval')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'Invalid Filter Value' in data
|
|
|
|
# float - not in list
|
|
rv = client.get('/admin/_float/?flt0_6=25.9%2C75.5')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' in data
|
|
assert 'test2_val_2' in data
|
|
assert 'test2_val_3' not in data
|
|
assert 'test2_val_4' not in data
|
|
|
|
# Test filters to joined table field
|
|
rv = client.get('/admin/_model2/?flt1_0=1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2_val_1' in data
|
|
assert 'test2_val_2' not in data
|
|
assert 'test2_val_3' not in data
|
|
assert 'test2_val_4' not in data
|
|
|
|
# Test human readable URLs
|
|
rv = client.get('/admin/_model3/?flt1_test1_equals=test1_val_1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test1_val_1' in data
|
|
assert 'test1_val_2' not in data
|
|
|
|
# Test date, time, and datetime filters
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view11._filter_groups[u'Date Field']] == \
|
|
[
|
|
(0, 'equals'),
|
|
(1, 'not equal'),
|
|
(2, 'greater than'),
|
|
(3, 'smaller than'),
|
|
(4, 'between'),
|
|
(5, 'not between'),
|
|
(6, 'empty'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view11._filter_groups[u'Datetime Field']] == \
|
|
[
|
|
(7, 'equals'),
|
|
(8, 'not equal'),
|
|
(9, 'greater than'),
|
|
(10, 'smaller than'),
|
|
(11, 'between'),
|
|
(12, 'not between'),
|
|
(13, 'empty'),
|
|
]
|
|
|
|
assert \
|
|
[(f['index'], f['operation']) for f in view11._filter_groups[u'Time Field']] == \
|
|
[
|
|
(14, 'equals'),
|
|
(15, 'not equal'),
|
|
(16, 'greater than'),
|
|
(17, 'smaller than'),
|
|
(18, 'between'),
|
|
(19, 'not between'),
|
|
(20, 'empty'),
|
|
]
|
|
|
|
# date - equals
|
|
rv = client.get('/admin/_datetime/?flt0_0=2014-11-17')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'date_obj1' in data
|
|
assert 'date_obj2' not in data
|
|
|
|
# date - not equal
|
|
rv = client.get('/admin/_datetime/?flt0_1=2014-11-17')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'date_obj1' not in data
|
|
assert 'date_obj2' in data
|
|
|
|
# date - greater
|
|
rv = client.get('/admin/_datetime/?flt0_2=2014-11-16')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'date_obj1' in data
|
|
assert 'date_obj2' not in data
|
|
|
|
# date - smaller
|
|
rv = client.get('/admin/_datetime/?flt0_3=2014-11-16')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'date_obj1' not in data
|
|
assert 'date_obj2' in data
|
|
|
|
# date - between
|
|
rv = client.get('/admin/_datetime/?flt0_4=2014-11-13+to+2014-11-20')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'date_obj1' in data
|
|
assert 'date_obj2' not in data
|
|
|
|
# date - not between
|
|
rv = client.get('/admin/_datetime/?flt0_5=2014-11-13+to+2014-11-20')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'date_obj1' not in data
|
|
assert 'date_obj2' in data
|
|
|
|
# date - empty
|
|
rv = client.get('/admin/_datetime/?flt0_6=1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test1_val_1' in data
|
|
assert 'date_obj1' not in data
|
|
assert 'date_obj2' not in data
|
|
|
|
# date - empty
|
|
rv = client.get('/admin/_datetime/?flt0_6=0')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test1_val_1' not in data
|
|
assert 'date_obj1' in data
|
|
assert 'date_obj2' in data
|
|
|
|
# datetime - equals
|
|
rv = client.get('/admin/_datetime/?flt0_7=2014-04-03+01%3A09%3A00')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'datetime_obj1' in data
|
|
assert 'datetime_obj2' not in data
|
|
|
|
# datetime - not equal
|
|
rv = client.get('/admin/_datetime/?flt0_8=2014-04-03+01%3A09%3A00')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'datetime_obj1' not in data
|
|
assert 'datetime_obj2' in data
|
|
|
|
# datetime - greater
|
|
rv = client.get('/admin/_datetime/?flt0_9=2014-04-03+01%3A08%3A00')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'datetime_obj1' in data
|
|
assert 'datetime_obj2' not in data
|
|
|
|
# datetime - smaller
|
|
rv = client.get('/admin/_datetime/?flt0_10=2014-04-03+01%3A08%3A00')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'datetime_obj1' not in data
|
|
assert 'datetime_obj2' in data
|
|
|
|
# datetime - between
|
|
rv = client.get('/admin/_datetime/?flt0_11=2014-04-02+00%3A00%3A00+to+2014-11-20+23%3A59%3A59')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'datetime_obj1' in data
|
|
assert 'datetime_obj2' not in data
|
|
|
|
# datetime - not between
|
|
rv = client.get('/admin/_datetime/?flt0_12=2014-04-02+00%3A00%3A00+to+2014-11-20+23%3A59%3A59')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'datetime_obj1' not in data
|
|
assert 'datetime_obj2' in data
|
|
|
|
# datetime - empty
|
|
rv = client.get('/admin/_datetime/?flt0_13=1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test1_val_1' in data
|
|
assert 'datetime_obj1' not in data
|
|
assert 'datetime_obj2' not in data
|
|
|
|
# datetime - not empty
|
|
rv = client.get('/admin/_datetime/?flt0_13=0')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test1_val_1' not in data
|
|
assert 'datetime_obj1' in data
|
|
assert 'datetime_obj2' in data
|
|
|
|
# time - equals
|
|
rv = client.get('/admin/_datetime/?flt0_14=11%3A10%3A09')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'timeonly_obj1' in data
|
|
assert 'timeonly_obj2' not in data
|
|
|
|
# time - not equal
|
|
rv = client.get('/admin/_datetime/?flt0_15=11%3A10%3A09')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'timeonly_obj1' not in data
|
|
assert 'timeonly_obj2' in data
|
|
|
|
# time - greater
|
|
rv = client.get('/admin/_datetime/?flt0_16=11%3A09%3A09')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'timeonly_obj1' in data
|
|
assert 'timeonly_obj2' not in data
|
|
|
|
# time - smaller
|
|
rv = client.get('/admin/_datetime/?flt0_17=11%3A09%3A09')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'timeonly_obj1' not in data
|
|
assert 'timeonly_obj2' in data
|
|
|
|
# time - between
|
|
rv = client.get('/admin/_datetime/?flt0_18=10%3A40%3A00+to+11%3A50%3A59')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'timeonly_obj1' in data
|
|
assert 'timeonly_obj2' not in data
|
|
|
|
# time - not between
|
|
rv = client.get('/admin/_datetime/?flt0_19=10%3A40%3A00+to+11%3A50%3A59')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'timeonly_obj1' not in data
|
|
assert 'timeonly_obj2' in data
|
|
|
|
# time - empty
|
|
rv = client.get('/admin/_datetime/?flt0_20=1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test1_val_1' in data
|
|
assert 'timeonly_obj1' not in data
|
|
assert 'timeonly_obj2' not in data
|
|
|
|
# time - not empty
|
|
rv = client.get('/admin/_datetime/?flt0_20=0')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test1_val_1' not in data
|
|
assert 'timeonly_obj1' in data
|
|
assert 'timeonly_obj2' in data
|
|
|
|
# Test enum filter
|
|
# enum - equals
|
|
rv = client.get('/admin/_enumfield/?flt0_0=model1_v1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'enum_obj1' in data
|
|
assert 'enum_obj2' not in data
|
|
|
|
# enum - not equal
|
|
rv = client.get('/admin/_enumfield/?flt0_1=model1_v1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'enum_obj1' not in data
|
|
assert 'enum_obj2' in data
|
|
|
|
# enum - empty
|
|
rv = client.get('/admin/_enumfield/?flt0_2=1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test1_val_1' in data
|
|
assert 'enum_obj1' not in data
|
|
assert 'enum_obj2' not in data
|
|
|
|
# enum - not empty
|
|
rv = client.get('/admin/_enumfield/?flt0_2=0')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test1_val_1' not in data
|
|
assert 'enum_obj1' in data
|
|
assert 'enum_obj2' in data
|
|
|
|
# enum - in list
|
|
rv = client.get('/admin/_enumfield/?flt0_3=model1_v1%2Cmodel1_v2')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test1_val_1' not in data
|
|
assert 'enum_obj1' in data
|
|
assert 'enum_obj2' in data
|
|
|
|
# enum - not in list
|
|
rv = client.get('/admin/_enumfield/?flt0_4=model1_v1%2Cmodel1_v2')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test1_val_1' in data
|
|
assert 'enum_obj1' not in data
|
|
assert 'enum_obj2' not in data
|
|
|
|
# Test single custom filter on relation
|
|
rv = client.get('/admin/_relation_test/?flt1_0=test1_val_1')
|
|
data = rv.data.decode('utf-8')
|
|
|
|
assert 'test1_val_1' in data
|
|
assert 'test1_val_2' not in data
|
|
|
|
|
|
def test_column_filters_sqla_obj():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
view = CustomModelView(
|
|
Model1, db.session,
|
|
column_filters=[Model1.test1]
|
|
)
|
|
admin.add_view(view)
|
|
|
|
assert len(view._filters) == 7
|
|
|
|
|
|
def test_hybrid_property():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
class Model1(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String)
|
|
width = db.Column(db.Integer)
|
|
height = db.Column(db.Integer)
|
|
|
|
@hybrid_property
|
|
def number_of_pixels(self):
|
|
return self.width * self.height
|
|
|
|
@hybrid_property
|
|
def number_of_pixels_str(self):
|
|
return str(self.number_of_pixels())
|
|
|
|
@number_of_pixels_str.expression
|
|
def number_of_pixels_str(cls):
|
|
return cast(cls.width * cls.height, db.String)
|
|
|
|
db.create_all()
|
|
|
|
assert tools.is_hybrid_property(Model1, 'number_of_pixels')
|
|
assert tools.is_hybrid_property(Model1, 'number_of_pixels_str')
|
|
assert not tools.is_hybrid_property(Model1, 'height')
|
|
assert not tools.is_hybrid_property(Model1, 'width')
|
|
|
|
db.session.add(Model1(id=1, name="test_row_1", width=25, height=25))
|
|
db.session.add(Model1(id=2, name="test_row_2", width=10, height=10))
|
|
db.session.commit()
|
|
|
|
client = app.test_client()
|
|
|
|
view = CustomModelView(
|
|
Model1, db.session,
|
|
column_default_sort='number_of_pixels',
|
|
column_filters=[filters.IntGreaterFilter(Model1.number_of_pixels,
|
|
'Number of Pixels')],
|
|
column_searchable_list=['number_of_pixels_str', ]
|
|
)
|
|
admin.add_view(view)
|
|
|
|
# filters - hybrid_property integer - greater
|
|
rv = client.get('/admin/model1/?flt0_0=600')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test_row_1' in data
|
|
assert 'test_row_2' not in data
|
|
|
|
# sorting
|
|
rv = client.get('/admin/model1/?sort=0')
|
|
assert rv.status_code == 200
|
|
|
|
_, data = view.get_list(0, None, None, None, None)
|
|
|
|
assert len(data) == 2
|
|
assert data[0].name == 'test_row_2'
|
|
assert data[1].name == 'test_row_1'
|
|
|
|
# searching
|
|
rv = client.get('/admin/model1/?search=100')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test_row_2' in data
|
|
assert 'test_row_1' not in data
|
|
|
|
|
|
def test_hybrid_property_nested():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
|
|
class Model1(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
firstname = db.Column(db.String)
|
|
lastname = db.Column(db.String)
|
|
|
|
@hybrid_property
|
|
def fullname(self):
|
|
return '{} {}'.format(self.firstname, self.lastname)
|
|
|
|
class Model2(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String)
|
|
owner_id = db.Column(db.Integer, db.ForeignKey('model1.id', ondelete='CASCADE'))
|
|
owner = db.relationship('Model1', backref=db.backref("tiles"), uselist=False)
|
|
|
|
db.create_all()
|
|
|
|
assert tools.is_hybrid_property(Model2, 'owner.fullname')
|
|
assert not tools.is_hybrid_property(Model2, 'owner.firstname')
|
|
|
|
db.session.add(Model1(id=1, firstname="John", lastname="Dow"))
|
|
db.session.add(Model1(id=2, firstname="Jim", lastname="Smith"))
|
|
db.session.add(Model2(id=1, name="pencil", owner_id=1))
|
|
db.session.add(Model2(id=2, name="key", owner_id=1))
|
|
db.session.add(Model2(id=3, name="map", owner_id=2))
|
|
db.session.commit()
|
|
|
|
client = app.test_client()
|
|
|
|
view = CustomModelView(
|
|
Model2, db.session,
|
|
column_list=('id', 'name', 'owner.fullname'),
|
|
column_default_sort='id',
|
|
)
|
|
admin.add_view(view)
|
|
|
|
rv = client.get('/admin/model2/')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'John Dow' in data
|
|
assert 'Jim Smith' in data
|
|
|
|
|
|
def test_url_args():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
view = CustomModelView(Model1, db.session,
|
|
page_size=2,
|
|
column_searchable_list=['test1'],
|
|
column_filters=['test1'])
|
|
admin.add_view(view)
|
|
|
|
db.session.add(Model1('data1'))
|
|
db.session.add(Model1('data2'))
|
|
db.session.add(Model1('data3'))
|
|
db.session.add(Model1('data4'))
|
|
db.session.commit()
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.get('/admin/model1/')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'data1' in data
|
|
assert 'data3' not in data
|
|
|
|
# page
|
|
rv = client.get('/admin/model1/?page=1')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'data1' not in data
|
|
assert 'data3' in data
|
|
|
|
# sort
|
|
rv = client.get('/admin/model1/?sort=0&desc=1')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'data1' not in data
|
|
assert 'data3' in data
|
|
assert 'data4' in data
|
|
|
|
# search
|
|
rv = client.get('/admin/model1/?search=data1')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'data1' in data
|
|
assert 'data2' not in data
|
|
|
|
rv = client.get('/admin/model1/?search=^data1')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'data2' not in data
|
|
|
|
# like
|
|
rv = client.get('/admin/model1/?flt0=0&flt0v=data1')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'data1' in data
|
|
|
|
# not like
|
|
rv = client.get('/admin/model1/?flt0=1&flt0v=data1')
|
|
data = rv.data.decode('utf-8')
|
|
assert 'data2' in data
|
|
|
|
|
|
def test_non_int_pk():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
|
|
class Model(db.Model):
|
|
id = db.Column(db.String, primary_key=True)
|
|
test = db.Column(db.String)
|
|
|
|
db.create_all()
|
|
|
|
view = CustomModelView(Model, db.session, form_columns=['id', 'test'])
|
|
admin.add_view(view)
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.get('/admin/model/')
|
|
assert rv.status_code == 200
|
|
|
|
rv = client.post('/admin/model/new/',
|
|
data=dict(id='test1', test='test2'))
|
|
assert rv.status_code == 302
|
|
|
|
rv = client.get('/admin/model/')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test1' in data
|
|
|
|
rv = client.get('/admin/model/edit/?id=test1')
|
|
assert rv.status_code == 200
|
|
data = rv.data.decode('utf-8')
|
|
assert 'test2' in data
|
|
|
|
|
|
def test_form_columns():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
|
|
class Model(db.Model):
|
|
id = db.Column(db.String, primary_key=True)
|
|
int_field = db.Column(db.Integer)
|
|
datetime_field = db.Column(db.DateTime)
|
|
text_field = db.Column(db.UnicodeText)
|
|
excluded_column = db.Column(db.String)
|
|
|
|
class ChildModel(db.Model):
|
|
class EnumChoices(enum.Enum):
|
|
first = 1
|
|
second = 2
|
|
|
|
id = db.Column(db.String, primary_key=True)
|
|
model_id = db.Column(db.Integer, db.ForeignKey(Model.id))
|
|
model = db.relationship(Model, backref='backref')
|
|
enum_field = db.Column(db.Enum('model1_v1', 'model1_v2'), nullable=True)
|
|
choice_field = db.Column(db.String, nullable=True)
|
|
sqla_utils_choice = db.Column(ChoiceType([
|
|
('choice-1', u'First choice'),
|
|
('choice-2', u'Second choice')
|
|
]))
|
|
sqla_utils_enum = db.Column(ChoiceType(EnumChoices, impl=db.Integer()))
|
|
|
|
db.create_all()
|
|
|
|
view1 = CustomModelView(Model, db.session, endpoint='view1',
|
|
form_columns=('int_field', 'text_field'))
|
|
view2 = CustomModelView(Model, db.session, endpoint='view2',
|
|
form_excluded_columns=('excluded_column',))
|
|
view3 = CustomModelView(ChildModel, db.session, endpoint='view3')
|
|
|
|
form1 = view1.create_form()
|
|
form2 = view2.create_form()
|
|
form3 = view3.create_form()
|
|
|
|
assert 'int_field' in form1._fields
|
|
assert 'text_field' in form1._fields
|
|
assert 'datetime_field' not in form1._fields
|
|
assert 'excluded_column' not in form2._fields
|
|
|
|
# check that relation shows up as a query select
|
|
assert type(form3.model).__name__ == 'QuerySelectField'
|
|
|
|
# check that select field is rendered if form_choices were specified
|
|
assert type(form3.choice_field).__name__ == 'Select2Field'
|
|
|
|
# check that select field is rendered for enum fields
|
|
assert type(form3.enum_field).__name__ == 'Select2Field'
|
|
|
|
# check that sqlalchemy_utils field types are handled appropriately
|
|
assert type(form3.sqla_utils_choice).__name__ == 'Select2Field'
|
|
assert type(form3.sqla_utils_enum).__name__ == 'Select2Field'
|
|
|
|
# test form_columns with model objects
|
|
view4 = CustomModelView(Model, db.session, endpoint='view1',
|
|
form_columns=[Model.int_field])
|
|
form4 = view4.create_form()
|
|
assert 'int_field' in form4._fields
|
|
|
|
|
|
@pytest.mark.xfail(raises=Exception)
|
|
def test_complex_form_columns():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
M1, M2 = create_models(db)
|
|
|
|
# test using a form column in another table
|
|
view = CustomModelView(M2, db.session, form_columns=['model1.test1'])
|
|
view.create_form()
|
|
|
|
|
|
def test_form_args():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
class Model(db.Model):
|
|
id = db.Column(db.String, primary_key=True)
|
|
test = db.Column(db.String, nullable=False)
|
|
|
|
db.create_all()
|
|
|
|
shared_form_args = {'test': {'validators': [validators.Regexp('test')]}}
|
|
|
|
view = CustomModelView(Model, db.session, form_args=shared_form_args)
|
|
admin.add_view(view)
|
|
|
|
create_form = view.create_form()
|
|
assert len(create_form.test.validators) == 2
|
|
|
|
# ensure shared field_args don't create duplicate validators
|
|
edit_form = view.edit_form()
|
|
assert len(edit_form.test.validators) == 2
|
|
|
|
|
|
def test_form_override():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
class Model(db.Model):
|
|
id = db.Column(db.String, primary_key=True)
|
|
test = db.Column(db.String)
|
|
|
|
db.create_all()
|
|
|
|
view1 = CustomModelView(Model, db.session, endpoint='view1')
|
|
view2 = CustomModelView(Model, db.session, endpoint='view2', form_overrides=dict(test=fields.FileField))
|
|
admin.add_view(view1)
|
|
admin.add_view(view2)
|
|
|
|
assert view1._create_form_class.test.field_class == fields.StringField
|
|
assert view2._create_form_class.test.field_class == fields.FileField
|
|
|
|
|
|
def test_form_onetoone():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
class Model1(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
test = db.Column(db.String)
|
|
|
|
class Model2(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
model1_id = db.Column(db.Integer, db.ForeignKey(Model1.id))
|
|
model1 = db.relationship(Model1, backref=db.backref('model2', uselist=False))
|
|
|
|
db.create_all()
|
|
|
|
view1 = CustomModelView(Model1, db.session, endpoint='view1')
|
|
view2 = CustomModelView(Model2, db.session, endpoint='view2')
|
|
admin.add_view(view1)
|
|
admin.add_view(view2)
|
|
|
|
model1 = Model1(test='test')
|
|
model2 = Model2(model1=model1)
|
|
db.session.add(model1)
|
|
db.session.add(model2)
|
|
db.session.commit()
|
|
|
|
assert model1.model2 == model2
|
|
assert model2.model1 == model1
|
|
|
|
assert not view1._create_form_class.model2.field_class.widget.multiple
|
|
assert not view2._create_form_class.model1.field_class.widget.multiple
|
|
|
|
|
|
def test_relations():
|
|
# TODO: test relations
|
|
pass
|
|
|
|
|
|
def test_on_model_change_delete():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, _ = create_models(db)
|
|
|
|
class ModelView(CustomModelView):
|
|
def on_model_change(self, form, model, is_created):
|
|
model.test1 = model.test1.upper()
|
|
|
|
def on_model_delete(self, model):
|
|
self.deleted = True
|
|
|
|
view = ModelView(Model1, db.session)
|
|
admin.add_view(view)
|
|
|
|
client = app.test_client()
|
|
|
|
client.post('/admin/model1/new/',
|
|
data=dict(test1='test1large', test2='test2'))
|
|
|
|
model = db.session.query(Model1).first()
|
|
assert model.test1 == 'TEST1LARGE'
|
|
|
|
url = '/admin/model1/edit/?id=%s' % model.id
|
|
client.post(url, data=dict(test1='test1small', test2='test2large'))
|
|
|
|
model = db.session.query(Model1).first()
|
|
assert model.test1 == 'TEST1SMALL'
|
|
|
|
url = '/admin/model1/delete/?id=%s' % model.id
|
|
client.post(url)
|
|
assert view.deleted
|
|
|
|
|
|
def test_multiple_delete():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
M1, _ = create_models(db)
|
|
|
|
db.session.add_all([M1('a'), M1('b'), M1('c')])
|
|
db.session.commit()
|
|
assert M1.query.count() == 3
|
|
|
|
view = ModelView(M1, db.session)
|
|
admin.add_view(view)
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.post('/admin/model1/action/', data=dict(action='delete', rowid=[1, 2, 3]))
|
|
assert rv.status_code == 302
|
|
assert M1.query.count() == 0
|
|
|
|
|
|
def test_default_sort():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
M1, _ = create_models(db)
|
|
|
|
db.session.add_all([M1('c', 'x'), M1('b', 'x'), M1('a', 'y')])
|
|
db.session.commit()
|
|
assert M1.query.count() == 3
|
|
|
|
view = CustomModelView(M1, db.session, column_default_sort='test1')
|
|
admin.add_view(view)
|
|
|
|
_, data = view.get_list(0, None, None, None, None)
|
|
|
|
assert len(data) == 3
|
|
assert data[0].test1 == 'a'
|
|
assert data[1].test1 == 'b'
|
|
assert data[2].test1 == 'c'
|
|
|
|
# test default sort on renamed columns - with column_list scaffolding
|
|
view2 = CustomModelView(M1, db.session, column_default_sort='test1',
|
|
column_labels={'test1': 'blah'}, endpoint='m1_2')
|
|
admin.add_view(view2)
|
|
|
|
_, data = view2.get_list(0, None, None, None, None)
|
|
|
|
assert len(data) == 3
|
|
assert data[0].test1 == 'a'
|
|
assert data[1].test1 == 'b'
|
|
assert data[2].test1 == 'c'
|
|
|
|
# test default sort on renamed columns - without column_list scaffolding
|
|
view3 = CustomModelView(M1, db.session, column_default_sort='test1',
|
|
column_labels={'test1': 'blah'}, endpoint='m1_3',
|
|
column_list=['test1'])
|
|
admin.add_view(view3)
|
|
|
|
_, data = view3.get_list(0, None, None, None, None)
|
|
|
|
assert len(data) == 3
|
|
assert data[0].test1 == 'a'
|
|
assert data[1].test1 == 'b'
|
|
assert data[2].test1 == 'c'
|
|
|
|
# test default sort with multiple columns
|
|
order = [('test2', False), ('test1', False)]
|
|
view4 = CustomModelView(M1, db.session, column_default_sort=order, endpoint='m1_4')
|
|
admin.add_view(view4)
|
|
|
|
_, data = view4.get_list(0, None, None, None, None)
|
|
|
|
assert len(data) == 3
|
|
assert data[0].test1 == 'b'
|
|
assert data[1].test1 == 'c'
|
|
assert data[2].test1 == 'a'
|
|
|
|
|
|
def test_complex_sort():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
M1, M2 = create_models(db)
|
|
|
|
m1 = M1(test1='c', test2='x')
|
|
db.session.add(m1)
|
|
db.session.add(M2('c', model1=m1))
|
|
|
|
m2 = M1(test1='b', test2='x')
|
|
db.session.add(m2)
|
|
db.session.add(M2('b', model1=m2))
|
|
|
|
m3 = M1(test1='a', test2='y')
|
|
db.session.add(m3)
|
|
db.session.add(M2('a', model1=m3))
|
|
|
|
db.session.commit()
|
|
|
|
# test sorting on relation string - 'model1.test1'
|
|
view = CustomModelView(M2, db.session,
|
|
column_list=['string_field', 'model1.test1'],
|
|
column_sortable_list=['model1.test1'])
|
|
admin.add_view(view)
|
|
view2 = CustomModelView(M2, db.session,
|
|
column_list=['string_field', 'model1'],
|
|
column_sortable_list=[('model1', ('model1.test2', 'model1.test1'))], endpoint="m1_2")
|
|
admin.add_view(view2)
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.get('/admin/model2/?sort=0')
|
|
assert rv.status_code == 200
|
|
|
|
_, data = view.get_list(0, 'model1.test1', False, None, None)
|
|
|
|
assert data[0].model1.test1 == 'a'
|
|
assert data[1].model1.test1 == 'b'
|
|
assert data[2].model1.test1 == 'c'
|
|
|
|
# test sorting on multiple columns in related model
|
|
rv = client.get('/admin/m1_2/?sort=0')
|
|
assert rv.status_code == 200
|
|
|
|
_, data = view2.get_list(0, 'model1', False, None, None)
|
|
|
|
assert data[0].model1.test1 == 'b'
|
|
assert data[1].model1.test1 == 'c'
|
|
assert data[2].model1.test1 == 'a'
|
|
|
|
|
|
@pytest.mark.xfail(raises=Exception)
|
|
def test_complex_sort_exception():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
M1, M2 = create_models(db)
|
|
|
|
# test column_sortable_list on a related table's column object
|
|
view = CustomModelView(M2, db.session, endpoint="model2_3",
|
|
column_sortable_list=[M1.test1])
|
|
admin.add_view(view)
|
|
|
|
sort_column = view._get_column_by_idx(0)[0]
|
|
_, data = view.get_list(0, sort_column, False, None, None)
|
|
|
|
assert len(data) == 2
|
|
assert data[0].model1.test1 == 'a'
|
|
assert data[1].model1.test1 == 'b'
|
|
|
|
|
|
def test_default_complex_sort():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
M1, M2 = create_models(db)
|
|
|
|
m1 = M1('b')
|
|
db.session.add(m1)
|
|
db.session.add(M2('c', model1=m1))
|
|
|
|
m2 = M1('a')
|
|
db.session.add(m2)
|
|
db.session.add(M2('c', model1=m2))
|
|
|
|
db.session.commit()
|
|
|
|
view = CustomModelView(M2, db.session, column_default_sort='model1.test1')
|
|
admin.add_view(view)
|
|
|
|
_, data = view.get_list(0, None, None, None, None)
|
|
|
|
assert len(data) == 2
|
|
assert data[0].model1.test1 == 'a'
|
|
assert data[1].model1.test1 == 'b'
|
|
|
|
# test column_default_sort on a related table's column object
|
|
view2 = CustomModelView(M2, db.session, endpoint="model2_2",
|
|
column_default_sort=(M1.test1, False))
|
|
admin.add_view(view2)
|
|
|
|
_, data = view2.get_list(0, None, None, None, None)
|
|
|
|
assert len(data) == 2
|
|
assert data[0].model1.test1 == 'a'
|
|
assert data[1].model1.test1 == 'b'
|
|
|
|
|
|
def test_extra_fields():
|
|
app, db, admin = setup()
|
|
with app.app_context():
|
|
Model1, _ = create_models(db)
|
|
|
|
view = CustomModelView(
|
|
Model1, db.session,
|
|
form_extra_fields={
|
|
'extra_field': fields.StringField('Extra Field')
|
|
}
|
|
)
|
|
admin.add_view(view)
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.get('/admin/model1/new/')
|
|
assert rv.status_code == 200
|
|
|
|
# Check presence and order
|
|
data = rv.data.decode('utf-8')
|
|
assert 'Extra Field' in data
|
|
pos1 = data.find('Extra Field')
|
|
pos2 = data.find('Test1')
|
|
assert pos2 < pos1
|
|
|
|
|
|
def test_extra_field_order():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, _ = create_models(db)
|
|
|
|
view = CustomModelView(
|
|
Model1, db.session,
|
|
form_columns=('extra_field', 'test1'),
|
|
form_extra_fields={
|
|
'extra_field': fields.StringField('Extra Field')
|
|
}
|
|
)
|
|
admin.add_view(view)
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.get('/admin/model1/new/')
|
|
assert rv.status_code == 200
|
|
|
|
# Check presence and order
|
|
data = rv.data.decode('utf-8')
|
|
pos1 = data.find('Extra Field')
|
|
pos2 = data.find('Test1')
|
|
assert pos2 > pos1
|
|
|
|
|
|
def test_modelview_localization():
|
|
def test_locale(locale):
|
|
try:
|
|
app, db, admin = setup()
|
|
|
|
app.config['BABEL_DEFAULT_LOCALE'] = locale
|
|
Babel(app)
|
|
|
|
with app.app_context():
|
|
Model1, _ = create_models(db)
|
|
|
|
view = CustomModelView(
|
|
Model1, db.session,
|
|
column_filters=['test1', 'bool_field', 'date_field', 'datetime_field', 'time_field']
|
|
)
|
|
|
|
admin.add_view(view)
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.get('/admin/model1/')
|
|
assert rv.status_code == 200
|
|
|
|
rv = client.get('/admin/model1/new/')
|
|
assert rv.status_code == 200
|
|
except:
|
|
print("Error on the following locale:", locale)
|
|
raise
|
|
|
|
locales = ['en', 'cs', 'de', 'es', 'fa', 'fr', 'pt', 'ru', 'zh_CN', 'zh_TW']
|
|
for locale in locales:
|
|
test_locale(locale)
|
|
|
|
|
|
def test_modelview_named_filter_localization():
|
|
app, db, admin = setup()
|
|
|
|
app.config['BABEL_DEFAULT_LOCALE'] = 'de'
|
|
Babel(app)
|
|
|
|
with app.app_context():
|
|
Model1, _ = create_models(db)
|
|
|
|
view = CustomModelView(
|
|
Model1, db.session,
|
|
named_filter_urls=True,
|
|
column_filters=['test1'],
|
|
)
|
|
|
|
filters = view.get_filters()
|
|
flt = filters[2]
|
|
with app.test_request_context():
|
|
flt_name = view.get_filter_arg(2, flt)
|
|
assert 'test1_equals' == flt_name
|
|
|
|
|
|
def test_custom_form_base():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
class TestForm(form.BaseForm):
|
|
pass
|
|
|
|
Model1, _ = create_models(db)
|
|
|
|
view = CustomModelView(
|
|
Model1, db.session,
|
|
form_base_class=TestForm
|
|
)
|
|
admin.add_view(view)
|
|
|
|
assert hasattr(view._create_form_class, 'test1')
|
|
|
|
create_form = view.create_form()
|
|
assert isinstance(create_form, TestForm)
|
|
|
|
|
|
def test_ajax_fk():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
view = CustomModelView(
|
|
Model2, db.session,
|
|
url='view',
|
|
form_ajax_refs={
|
|
'model1': {
|
|
'fields': ('test1', 'test2')
|
|
}
|
|
}
|
|
)
|
|
admin.add_view(view)
|
|
|
|
assert u'model1' in view._form_ajax_refs
|
|
|
|
model = Model1(u'first')
|
|
model2 = Model1(u'foo', u'bar')
|
|
db.session.add_all([model, model2])
|
|
db.session.commit()
|
|
|
|
# Check loader
|
|
loader = view._form_ajax_refs[u'model1']
|
|
mdl = loader.get_one(model.id)
|
|
assert mdl.test1 == model.test1
|
|
|
|
items = loader.get_list(u'fir')
|
|
assert len(items) == 1
|
|
assert items[0].id == model.id
|
|
|
|
items = loader.get_list(u'bar')
|
|
assert len(items) == 1
|
|
assert items[0].test1 == u'foo'
|
|
|
|
# Check form generation
|
|
form = view.create_form()
|
|
assert form.model1.__class__.__name__ == u'AjaxSelectField'
|
|
|
|
with app.test_request_context('/admin/view/'):
|
|
assert u'value=""' not in form.model1()
|
|
|
|
form.model1.data = model
|
|
assert (u'data-json="[%s, "first"]"' % model.id in form.model1() or
|
|
u'data-json="[%s, "first"]"' % model.id in form.model1())
|
|
assert u'value="1"' in form.model1()
|
|
|
|
# Check querying
|
|
client = app.test_client()
|
|
|
|
req = client.get(u'/admin/view/ajax/lookup/?name=model1&query=foo')
|
|
assert req.data.decode('utf-8') == u'[[%s, "foo"]]' % model2.id
|
|
|
|
# Check submitting
|
|
req = client.post('/admin/view/new/', data={u'model1': as_unicode(model.id)})
|
|
mdl = db.session.query(Model2).first()
|
|
|
|
assert mdl is not None
|
|
assert mdl.model1 is not None
|
|
assert mdl.model1.id == model.id
|
|
assert mdl.model1.test1 == u'first'
|
|
|
|
|
|
def test_ajax_fk_multi():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
class Model1(db.Model):
|
|
__tablename__ = 'model1'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(20))
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
table = db.Table('m2m', db.Model.metadata,
|
|
db.Column('model1_id', db.Integer, db.ForeignKey('model1.id')),
|
|
db.Column('model2_id', db.Integer, db.ForeignKey('model2.id'))
|
|
)
|
|
|
|
class Model2(db.Model):
|
|
__tablename__ = 'model2'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(20))
|
|
|
|
model1_id = db.Column(db.Integer(), db.ForeignKey(Model1.id))
|
|
model1 = db.relationship(Model1, backref='models2', secondary=table)
|
|
|
|
db.create_all()
|
|
|
|
view = CustomModelView(
|
|
Model2, db.session,
|
|
url='view',
|
|
form_ajax_refs={
|
|
'model1': {
|
|
'fields': ['name']
|
|
}
|
|
}
|
|
)
|
|
admin.add_view(view)
|
|
|
|
assert u'model1' in view._form_ajax_refs
|
|
|
|
model = Model1(name=u'first')
|
|
db.session.add_all([model, Model1(name=u'foo')])
|
|
db.session.commit()
|
|
|
|
# Check form generation
|
|
form = view.create_form()
|
|
assert form.model1.__class__.__name__ == u'AjaxSelectMultipleField'
|
|
|
|
with app.test_request_context('/admin/view/'):
|
|
assert u'data-json="[]"' in form.model1()
|
|
|
|
form.model1.data = [model]
|
|
assert (u'data-json="[[1, "first"]]"' in form.model1() or
|
|
u'data-json="[[1, "first"]]"' in form.model1())
|
|
|
|
# Check submitting
|
|
client = app.test_client()
|
|
client.post('/admin/view/new/', data={u'model1': as_unicode(model.id)})
|
|
mdl = db.session.query(Model2).first()
|
|
|
|
assert mdl is not None
|
|
assert mdl.model1 is not None
|
|
assert len(mdl.model1) == 1
|
|
|
|
|
|
def test_safe_redirect():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, _ = create_models(db)
|
|
|
|
view = CustomModelView(Model1, db.session)
|
|
admin.add_view(view)
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.post('/admin/model1/new/?url=http://localhost/admin/model2view/',
|
|
data=dict(test1='test1large', test2='test2',
|
|
_continue_editing='Save and Continue Editing'))
|
|
|
|
assert rv.status_code == 302
|
|
|
|
# werkzeug 2.1.0+ now returns *relative* redirect/location by default.
|
|
expected = '/admin/model1/edit/'
|
|
|
|
# handle old werkzeug (or if relative location is disabled via `autocorrect_location_header=True`)
|
|
if not hasattr(rv, 'autocorrect_location_header') or rv.autocorrect_location_header:
|
|
expected = 'http://localhost' + expected
|
|
|
|
assert rv.location.startswith(expected)
|
|
assert 'url=http%3A%2F%2Flocalhost%2Fadmin%2Fmodel2view%2F' in rv.location
|
|
assert 'id=1' in rv.location
|
|
|
|
rv = client.post('/admin/model1/new/?url=http://google.com/evil/',
|
|
data=dict(test1='test1large', test2='test2',
|
|
_continue_editing='Save and Continue Editing'))
|
|
|
|
assert rv.status_code == 302
|
|
assert rv.location.startswith(expected)
|
|
assert 'url=%2Fadmin%2Fmodel1%2F' in rv.location
|
|
assert 'id=2' in rv.location
|
|
|
|
|
|
def test_simple_list_pager():
|
|
app, db, admin = setup()
|
|
with app.app_context():
|
|
Model1, _ = create_models(db)
|
|
|
|
class TestModelView(CustomModelView):
|
|
simple_list_pager = True
|
|
|
|
def get_count_query(self):
|
|
assert False
|
|
|
|
view = TestModelView(Model1, db.session)
|
|
admin.add_view(view)
|
|
|
|
count, data = view.get_list(0, None, None, None, None)
|
|
assert count is None
|
|
|
|
|
|
def test_unlimited_page_size():
|
|
app, db, admin = setup()
|
|
with app.app_context():
|
|
M1, _ = create_models(db)
|
|
|
|
db.session.add_all([M1('1'), M1('2'), M1('3'), M1('4'), M1('5'), M1('6'),
|
|
M1('7'), M1('8'), M1('9'), M1('10'), M1('11'),
|
|
M1('12'), M1('13'), M1('14'), M1('15'), M1('16'),
|
|
M1('17'), M1('18'), M1('19'), M1('20'), M1('21')])
|
|
|
|
view = CustomModelView(M1, db.session)
|
|
|
|
# test 0 as page_size
|
|
_, data = view.get_list(0, None, None, None, None, execute=True,
|
|
page_size=0)
|
|
assert len(data) == 21
|
|
|
|
# test False as page_size
|
|
_, data = view.get_list(0, None, None, None, None, execute=True,
|
|
page_size=False)
|
|
assert len(data) == 21
|
|
|
|
|
|
def test_advanced_joins():
|
|
app, db, admin = setup()
|
|
with app.app_context():
|
|
class Model1(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
val1 = db.Column(db.String(20))
|
|
test = db.Column(db.String(20))
|
|
|
|
class Model2(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
val2 = db.Column(db.String(20))
|
|
|
|
model1_id = db.Column(db.Integer, db.ForeignKey(Model1.id))
|
|
model1 = db.relationship(Model1, backref='model2')
|
|
|
|
class Model3(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
val2 = db.Column(db.String(20))
|
|
|
|
model2_id = db.Column(db.Integer, db.ForeignKey(Model2.id))
|
|
model2 = db.relationship(Model2, backref='model3')
|
|
|
|
view1 = CustomModelView(Model1, db.session)
|
|
admin.add_view(view1)
|
|
|
|
view2 = CustomModelView(Model2, db.session)
|
|
admin.add_view(view2)
|
|
|
|
view3 = CustomModelView(Model3, db.session)
|
|
admin.add_view(view3)
|
|
|
|
# Test joins
|
|
attr, path = tools.get_field_with_path(Model2, 'model1.val1')
|
|
assert attr == Model1.val1
|
|
assert path == [Model2.model1]
|
|
|
|
attr, path = tools.get_field_with_path(Model1, 'model2.val2')
|
|
assert attr == Model2.val2
|
|
assert id(path[0]) == id(Model1.model2)
|
|
|
|
attr, path = tools.get_field_with_path(Model3, 'model2.model1.val1')
|
|
assert attr == Model1.val1
|
|
assert path == [Model3.model2, Model2.model1]
|
|
|
|
# Test how joins are applied
|
|
query = view3.get_query()
|
|
|
|
joins = {}
|
|
q1, joins, alias = view3._apply_path_joins(query, joins, path)
|
|
assert (True, Model3.model2) in joins
|
|
assert (True, Model2.model1) in joins
|
|
assert alias is not None
|
|
|
|
# Check if another join would use same path
|
|
attr, path = tools.get_field_with_path(Model2, 'model1.test')
|
|
q2, joins, alias = view2._apply_path_joins(query, joins, path)
|
|
|
|
assert len(joins) == 2
|
|
|
|
if hasattr(q2, '_join_entities'):
|
|
for p in q2._join_entities:
|
|
assert p in q1._join_entities
|
|
|
|
assert alias is not None
|
|
|
|
# Check if normal properties are supported by tools.get_field_with_path
|
|
attr, path = tools.get_field_with_path(Model2, Model1.test)
|
|
assert attr == Model1.test
|
|
assert path == [Model1.__table__]
|
|
|
|
q3, joins, alias = view2._apply_path_joins(view2.get_query(), joins, path)
|
|
assert len(joins) == 3
|
|
assert alias is None
|
|
|
|
|
|
def test_multipath_joins():
|
|
app, db, admin = setup()
|
|
with app.app_context():
|
|
class Model1(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
val1 = db.Column(db.String(20))
|
|
test = db.Column(db.String(20))
|
|
|
|
class Model2(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
val2 = db.Column(db.String(20))
|
|
|
|
first_id = db.Column(db.Integer, db.ForeignKey(Model1.id))
|
|
first = db.relationship(Model1, backref='first', foreign_keys=[first_id])
|
|
|
|
second_id = db.Column(db.Integer, db.ForeignKey(Model1.id))
|
|
second = db.relationship(Model1, backref='second', foreign_keys=[second_id])
|
|
|
|
db.create_all()
|
|
|
|
view = CustomModelView(Model2, db.session, filters=['first.test'])
|
|
admin.add_view(view)
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.get('/admin/model2/')
|
|
assert rv.status_code == 200
|
|
|
|
|
|
# TODO: Why this fails?
|
|
@pytest.mark.xfail(raises=Exception)
|
|
def test_different_bind_joins():
|
|
app, db, admin = setup()
|
|
app.config['SQLALCHEMY_BINDS'] = {
|
|
'other': 'sqlite:///'
|
|
}
|
|
|
|
with app.app_context():
|
|
class Model1(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
val1 = db.Column(db.String(20))
|
|
|
|
class Model2(db.Model):
|
|
__bind_key__ = 'other'
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
val1 = db.Column(db.String(20))
|
|
first_id = db.Column(db.Integer, db.ForeignKey(Model1.id))
|
|
first = db.relationship(Model1)
|
|
|
|
db.create_all()
|
|
|
|
view = CustomModelView(Model2, db.session)
|
|
admin.add_view(view)
|
|
|
|
client = app.test_client()
|
|
|
|
rv = client.get('/admin/model2/')
|
|
assert rv.status_code == 200
|
|
|
|
|
|
def test_model_default():
|
|
app, db, admin = setup()
|
|
with app.app_context():
|
|
_, Model2 = create_models(db)
|
|
|
|
class ModelView(CustomModelView):
|
|
pass
|
|
|
|
view = ModelView(Model2, db.session)
|
|
admin.add_view(view)
|
|
|
|
client = app.test_client()
|
|
rv = client.post('/admin/model2/new/', data=dict())
|
|
assert b'This field is required' not in rv.data
|
|
|
|
|
|
def test_export_csv():
|
|
app, db, admin = setup()
|
|
|
|
with app.app_context():
|
|
Model1, Model2 = create_models(db)
|
|
|
|
for x in range(5):
|
|
fill_db(db, Model1, Model2)
|
|
|
|
view1 = CustomModelView(Model1, db.session, can_export=True,
|
|
column_list=['test1', 'test2'], export_max_rows=2,
|
|
endpoint='row_limit_2')
|
|
admin.add_view(view1)
|
|
view2 = CustomModelView(Model1, db.session, can_export=True,
|
|
column_list=['test1', 'test2'],
|
|
endpoint='no_row_limit')
|
|
admin.add_view(view2)
|
|
|
|
client = app.test_client()
|
|
|
|
# test export_max_rows
|
|
rv = client.get('/admin/row_limit_2/export/csv/')
|
|
data = rv.data.decode('utf-8')
|
|
assert rv.status_code == 200
|
|
assert "Test1,Test2\r\n" + \
|
|
"test1_val_1,test2_val_1\r\n" + \
|
|
"test1_val_2,test2_val_2\r\n" == data
|
|
|
|
# test row limit without export_max_rows
|
|
rv = client.get('/admin/no_row_limit/export/csv/')
|
|
data = rv.data.decode('utf-8')
|
|
assert rv.status_code == 200
|
|
assert len(data.splitlines()) > 21
|