From 4c723b2bb48bbcf534652b2e7cbdf8a562c27bfc Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 17 Jul 2024 17:52:48 +0100 Subject: [PATCH] Use pytest fixtures instead of setup functions This allows us to have (almost) a single place where we have to think about Babel (in the `babel`) fixture. This should make it easier for us to, in the future, run tests without babel or flask-babel installed, to validate flask-admin for users without that package. This is important because the `flask_admin.babel` module has two clearly different codepaths depending on whether the package is installed. Importantly, because of the fixture chaining (`admin` requires `babel`), it also means that `babel` (if available) is always configured for every flask app instance for every test. --- flask_admin/tests/conftest.py | 30 +++ flask_admin/tests/fileadmin/__init__.py | 11 - flask_admin/tests/fileadmin/test_fileadmin.py | 41 ++- flask_admin/tests/geoa/__init__.py | 20 -- flask_admin/tests/geoa/conftest.py | 24 ++ flask_admin/tests/geoa/test_basic.py | 8 +- flask_admin/tests/mongoengine/__init__.py | 17 -- flask_admin/tests/mongoengine/conftest.py | 20 ++ flask_admin/tests/mongoengine/test_basic.py | 88 ++----- flask_admin/tests/peeweemodel/__init__.py | 15 -- flask_admin/tests/peeweemodel/conftest.py | 18 ++ flask_admin/tests/peeweemodel/test_basic.py | 40 +-- flask_admin/tests/pymongo/__init__.py | 17 -- flask_admin/tests/pymongo/conftest.py | 17 ++ flask_admin/tests/pymongo/test_basic.py | 6 +- flask_admin/tests/sqla/__init__.py | 33 --- flask_admin/tests/sqla/conftest.py | 48 ++++ flask_admin/tests/sqla/test_basic.py | 241 +++++++----------- flask_admin/tests/sqla/test_form_rules.py | 25 +- flask_admin/tests/sqla/test_inlineform.py | 21 +- flask_admin/tests/sqla/test_multi_pk.py | 21 +- flask_admin/tests/sqla/test_postgres.py | 50 ++-- flask_admin/tests/sqla/test_translation.py | 15 +- flask_admin/tests/test_base.py | 94 +++---- flask_admin/tests/test_form_upload.py | 5 +- flask_admin/tests/test_model.py | 91 ++----- 26 files changed, 414 insertions(+), 602 deletions(-) create mode 100644 flask_admin/tests/conftest.py create mode 100644 flask_admin/tests/geoa/conftest.py create mode 100644 flask_admin/tests/mongoengine/conftest.py create mode 100644 flask_admin/tests/peeweemodel/conftest.py create mode 100644 flask_admin/tests/pymongo/conftest.py create mode 100644 flask_admin/tests/sqla/conftest.py diff --git a/flask_admin/tests/conftest.py b/flask_admin/tests/conftest.py new file mode 100644 index 000000000..674c21919 --- /dev/null +++ b/flask_admin/tests/conftest.py @@ -0,0 +1,30 @@ +import pytest +from flask import Flask + +from flask_admin import Admin + + +@pytest.fixture(scope='function') +def app(): + app = Flask(__name__) + app.config['SECRET_KEY'] = '1' + app.config['WTF_CSRF_ENABLED'] = False + + yield app + + +@pytest.fixture +def babel(app): + try: + from flask_babel import Babel + _ = Babel(app) + except ImportError: + pass + + yield babel + + +@pytest.fixture +def admin(app, babel): + admin = Admin(app) + yield admin diff --git a/flask_admin/tests/fileadmin/__init__.py b/flask_admin/tests/fileadmin/__init__.py index 3f34de3b5..e69de29bb 100644 --- a/flask_admin/tests/fileadmin/__init__.py +++ b/flask_admin/tests/fileadmin/__init__.py @@ -1,11 +0,0 @@ -from flask import Flask -from flask_admin import Admin - - -def setup(): - app = Flask(__name__) - app.config['SECRET_KEY'] = '1' - app.config['CSRF_ENABLED'] = False - - admin = Admin(app) - return app, admin diff --git a/flask_admin/tests/fileadmin/test_fileadmin.py b/flask_admin/tests/fileadmin/test_fileadmin.py index 41a419609..0b01e1589 100644 --- a/flask_admin/tests/fileadmin/test_fileadmin.py +++ b/flask_admin/tests/fileadmin/test_fileadmin.py @@ -17,12 +17,10 @@ def fileadmin_class(self): def fileadmin_args(self): raise NotImplementedError - def test_file_admin(self): + def test_file_admin(self, app, admin): fileadmin_class = self.fileadmin_class() fileadmin_args, fileadmin_kwargs = self.fileadmin_args() - app, admin = setup() - class MyFileAdmin(fileadmin_class): editable_extensions = ('txt',) @@ -129,10 +127,8 @@ class MyFileAdmin(fileadmin_class): assert 'path=dummy_renamed_dir' not in rv.data.decode('utf-8') assert 'path=dummy.txt' in rv.data.decode('utf-8') - def test_modal_edit(self): - # bootstrap 2 - test edit_modal - app_bs2 = Flask(__name__) - admin_bs2 = Admin(app_bs2, template_mode="bootstrap2") + def test_modal_edit_bs2(self, app, babel): + admin_bs2 = Admin(app, template_mode="bootstrap2") fileadmin_class = self.fileadmin_class() fileadmin_args, fileadmin_kwargs = self.fileadmin_args() @@ -156,7 +152,7 @@ class EditModalOff(fileadmin_class): admin_bs2.add_view(edit_modal_on) admin_bs2.add_view(edit_modal_off) - client_bs2 = app_bs2.test_client() + client_bs2 = app.test_client() # bootstrap 2 - ensure modal window is added when edit_modal is # enabled @@ -171,14 +167,32 @@ class EditModalOff(fileadmin_class): data = rv.data.decode('utf-8') assert 'fa_modal_window' not in data - # bootstrap 3 - app_bs3 = Flask(__name__) - admin_bs3 = Admin(app_bs3, template_mode="bootstrap3") + def test_modal_edit_bs3(self, app, babel): + admin_bs3 = Admin(app, template_mode="bootstrap3") + + fileadmin_class = self.fileadmin_class() + fileadmin_args, fileadmin_kwargs = self.fileadmin_args() + + class EditModalOn(fileadmin_class): + edit_modal = True + editable_extensions = ('txt',) + + class EditModalOff(fileadmin_class): + edit_modal = False + editable_extensions = ('txt',) + + on_view_kwargs = dict(fileadmin_kwargs) + on_view_kwargs.setdefault('endpoint', 'edit_modal_on') + edit_modal_on = EditModalOn(*fileadmin_args, **on_view_kwargs) + + off_view_kwargs = dict(fileadmin_kwargs) + off_view_kwargs.setdefault('endpoint', 'edit_modal_off') + edit_modal_off = EditModalOff(*fileadmin_args, **off_view_kwargs) admin_bs3.add_view(edit_modal_on) admin_bs3.add_view(edit_modal_off) - client_bs3 = app_bs3.test_client() + client_bs3 = app.test_client() # bootstrap 3 - ensure modal window is added when edit_modal is # enabled @@ -201,10 +215,9 @@ def fileadmin_class(self): def fileadmin_args(self): return (self._test_files_root, '/files'), {} - def test_fileadmin_sort_bogus_url_param(self): + def test_fileadmin_sort_bogus_url_param(self, app, admin): fileadmin_class = self.fileadmin_class() fileadmin_args, fileadmin_kwargs = self.fileadmin_args() - app, admin = setup() class MyFileAdmin(fileadmin_class): editable_extensions = ('txt',) diff --git a/flask_admin/tests/geoa/__init__.py b/flask_admin/tests/geoa/__init__.py index 2d754d8a3..e69de29bb 100644 --- a/flask_admin/tests/geoa/__init__.py +++ b/flask_admin/tests/geoa/__init__.py @@ -1,20 +0,0 @@ -from flask import Flask -from flask_admin import Admin -from flask_sqlalchemy import SQLAlchemy - - -def setup(): - app = Flask(__name__) - app.config['SECRET_KEY'] = '1' - app.config['CSRF_ENABLED'] = False - app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:postgres@localhost/flask_admin_test' - app.config['SQLALCHEMY_ECHO'] = True - app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False - - db = SQLAlchemy() - db.init_app(app) - admin = Admin(app) - - app.app_context().push() - - return app, db, admin diff --git a/flask_admin/tests/geoa/conftest.py b/flask_admin/tests/geoa/conftest.py new file mode 100644 index 000000000..f0ed17354 --- /dev/null +++ b/flask_admin/tests/geoa/conftest.py @@ -0,0 +1,24 @@ +import pytest + +from flask_admin import Admin +from flask_sqlalchemy import SQLAlchemy + + +@pytest.fixture +def db(): + db = SQLAlchemy() + yield db + + +@pytest.fixture +def admin(app, babel, db): + app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:postgres@localhost/flask_admin_test' + app.config['SQLALCHEMY_ECHO'] = True + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + + db.init_app(app) + + app.app_context().push() + + admin = Admin(app) + yield admin diff --git a/flask_admin/tests/geoa/test_basic.py b/flask_admin/tests/geoa/test_basic.py index 7009107b2..650d71660 100644 --- a/flask_admin/tests/geoa/test_basic.py +++ b/flask_admin/tests/geoa/test_basic.py @@ -9,8 +9,6 @@ from geoalchemy2 import Geometry from geoalchemy2.shape import to_shape -from . import setup - def create_models(db): class GeoModel(db.Model): @@ -28,8 +26,7 @@ def __unicode__(self): @pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") -def test_model(): - app, db, admin = setup() +def test_model(app, db, admin): GeoModel = create_models(db) with app.app_context(): db.create_all() @@ -128,8 +125,7 @@ def test_model(): assert db.session.query(GeoModel).count() == 0 -def test_none(): - app, db, admin = setup() +def test_none(app, db, admin): GeoModel = create_models(db) with app.app_context(): db.create_all() diff --git a/flask_admin/tests/mongoengine/__init__.py b/flask_admin/tests/mongoengine/__init__.py index 8a8bb9432..e69de29bb 100644 --- a/flask_admin/tests/mongoengine/__init__.py +++ b/flask_admin/tests/mongoengine/__init__.py @@ -1,17 +0,0 @@ -from flask import Flask -from flask_admin import Admin -from flask_mongoengine import MongoEngine - - -def setup(): - app = Flask(__name__) - app.config['SECRET_KEY'] = '1' - app.config['CSRF_ENABLED'] = False - app.config['MONGODB_SETTINGS'] = {'DB': 'tests'} - - db = MongoEngine() - db.init_app(app) - - admin = Admin(app) - - return app, db, admin diff --git a/flask_admin/tests/mongoengine/conftest.py b/flask_admin/tests/mongoengine/conftest.py new file mode 100644 index 000000000..5a504e7f8 --- /dev/null +++ b/flask_admin/tests/mongoengine/conftest.py @@ -0,0 +1,20 @@ +import pytest + +from flask_admin import Admin +from flask_mongoengine import MongoEngine + + +@pytest.fixture +def db(): + db = MongoEngine() + yield db + + +@pytest.fixture +def admin(app, babel, db): + app.config['MONGODB_SETTINGS'] = {'DB': 'tests'} + + db.init_app(app) + + admin = Admin(app) + yield admin diff --git a/flask_admin/tests/mongoengine/test_basic.py b/flask_admin/tests/mongoengine/test_basic.py index 4bc5e1ed4..98b10d328 100644 --- a/flask_admin/tests/mongoengine/test_basic.py +++ b/flask_admin/tests/mongoengine/test_basic.py @@ -1,12 +1,9 @@ -import pytest from wtforms import fields, validators from flask_admin import form from flask_admin._compat import as_unicode from flask_admin.contrib.mongoengine import ModelView -from . import setup - from datetime import datetime @@ -72,9 +69,7 @@ def fill_db(Model1, Model2): datetime_field=datetime(2013, 3, 2, 0, 8, 0)).save() -def test_model(): - app, db, admin = setup() - +def test_model(app, db, admin): Model1, Model2 = create_models(db) view = CustomModelView(Model1) @@ -146,9 +141,7 @@ def test_model(): assert Model1.objects.count() == 0 -def test_column_editable_list(): - app, db, admin = setup() - +def test_column_editable_list(app, db, admin): Model1, Model2 = create_models(db) view = CustomModelView(Model1, @@ -220,9 +213,7 @@ def test_column_editable_list(): assert 'test1_val_1' in data -def test_details_view(): - app, db, admin = setup() - +def test_details_view(app, db, admin): Model1, Model2 = create_models(db) view_no_details = CustomModelView(Model1) @@ -277,9 +268,7 @@ def test_details_view(): assert 'Int Field' not in data -def test_column_filters(): - app, db, admin = setup() - +def test_column_filters(app, db, admin): Model1, Model2 = create_models(db) # fill DB with values @@ -684,8 +673,7 @@ def test_column_filters(): assert 'datetime_obj2' in data -def test_default_sort(): - app, db, admin = setup() +def test_default_sort(app, db, admin): M1, _ = create_models(db) M1(test1='c', test2='x').save() @@ -716,9 +704,7 @@ def test_default_sort(): assert data[2].test1 == 'a' -def test_extra_fields(): - app, db, admin = setup() - +def test_extra_fields(app, db, admin): Model1, _ = create_models(db) view = CustomModelView( @@ -742,9 +728,7 @@ def test_extra_fields(): assert pos2 < pos1 -def test_extra_field_order(): - app, db, admin = setup() - +def test_extra_field_order(app, db, admin): Model1, _ = create_models(db) view = CustomModelView( @@ -768,9 +752,7 @@ def test_extra_field_order(): assert pos2 < pos1 -def test_custom_form_base(): - app, db, admin = setup() - +def test_custom_form_base(app, db, admin): class TestForm(form.BaseForm): pass @@ -788,9 +770,7 @@ class TestForm(form.BaseForm): assert isinstance(create_form, TestForm) -def test_subdocument_config(): - app, db, admin = setup() - +def test_subdocument_config(app, db, admin): class Comment(db.EmbeddedDocument): name = db.StringField(max_length=20, required=True) value = db.StringField(max_length=20) @@ -830,9 +810,7 @@ class Model1(db.Document): assert 'value' not in dir(form.subdoc.form) -def test_subdocument_class_config(): - app, db, admin = setup() - +def test_subdocument_class_config(app, db, admin): from flask_admin.contrib.mongoengine import EmbeddedForm class Comment(db.EmbeddedDocument): @@ -859,9 +837,7 @@ class EmbeddedConfig(EmbeddedForm): assert 'value' not in dir(form.subdoc.form) -def test_nested_subdocument_config(): - app, db, admin = setup() - +def test_nested_subdocument_config(app, db, admin): # Check recursive class Comment(db.EmbeddedDocument): name = db.StringField(max_length=20, required=True) @@ -893,9 +869,7 @@ class Model1(db.Document): assert 'value' not in dir(form.nested.form.comment.form) -def test_nested_list_subdocument(): - app, db, admin = setup() - +def test_nested_list_subdocument(app, db, admin): class Comment(db.EmbeddedDocument): name = db.StringField(max_length=20, required=True) value = db.StringField(max_length=20) @@ -926,9 +900,7 @@ class Model1(db.Document): assert 'value' not in dir(inline_form) -def test_nested_sortedlist_subdocument(): - app, db, admin = setup() - +def test_nested_sortedlist_subdocument(app, db, admin): class Comment(db.EmbeddedDocument): name = db.StringField(max_length=20, required=True) value = db.StringField(max_length=20) @@ -958,9 +930,7 @@ class Model1(db.Document): assert 'value' not in dir(inline_form) -def test_sortedlist_subdocument_validation(): - app, db, admin = setup() - +def test_sortedlist_subdocument_validation(app, db, admin): class Comment(db.EmbeddedDocument): name = db.StringField(max_length=20, required=True) value = db.StringField(max_length=20) @@ -983,9 +953,7 @@ class Model1(db.Document): assert b'This field is required' in rv.data -def test_list_subdocument_validation(): - app, db, admin = setup() - +def test_list_subdocument_validation(app, db, admin): class Comment(db.EmbeddedDocument): name = db.StringField(max_length=20, required=True) value = db.StringField(max_length=20) @@ -1008,9 +976,7 @@ class Model1(db.Document): assert b'This field is required' in rv.data -def test_ajax_fk(): - app, db, admin = setup() - +def test_ajax_fk(app, db, admin): Model1, Model2 = create_models(db) view = CustomModelView( @@ -1071,9 +1037,7 @@ def test_ajax_fk(): assert mdl.model1.test1 == u'first' -def test_nested_ajax_refs(): - app, db, admin = setup() - +def test_nested_ajax_refs(app, db, admin): # Check recursive class Comment(db.Document): name = db.StringField(max_length=20, required=True) @@ -1105,9 +1069,7 @@ class Model1(db.Document): assert 'nested-comment' in view1._form_ajax_refs -def test_form_flat_choices(): - app, db, admin = setup() - +def test_form_flat_choices(app, db, admin): class Model(db.Document): name = db.StringField(max_length=20, choices=('a', 'b', 'c')) @@ -1118,9 +1080,7 @@ class Model(db.Document): assert form.name.choices == [('a', 'a'), ('b', 'b'), ('c', 'c')] -def test_form_args(): - app, db, admin = setup() - +def test_form_args(app, db, admin): class Model(db.Document): test = db.StringField(required=True) @@ -1137,9 +1097,7 @@ class Model(db.Document): assert len(edit_form.test.validators) == 2 -def test_form_args_embeddeddoc(): - app, db, admin = setup() - +def test_form_args_embeddeddoc(app, db, admin): class Info(db.EmbeddedDocument): name = db.StringField() age = db.StringField() @@ -1162,8 +1120,7 @@ class Model(db.Document): assert form.info.label.text == 'Information' -def test_simple_list_pager(): - app, db, admin = setup() +def test_simple_list_pager(app, db, admin): Model1, _ = create_models(db) class TestModelView(CustomModelView): @@ -1179,8 +1136,7 @@ def get_count_query(self): assert count is None -def test_export_csv(): - app, db, admin = setup() +def test_export_csv(app, db, admin): Model1, Model2 = create_models(db) view = CustomModelView(Model1, can_export=True, diff --git a/flask_admin/tests/peeweemodel/__init__.py b/flask_admin/tests/peeweemodel/__init__.py index 20fe5543c..e69de29bb 100644 --- a/flask_admin/tests/peeweemodel/__init__.py +++ b/flask_admin/tests/peeweemodel/__init__.py @@ -1,15 +0,0 @@ -from flask import Flask -from flask_admin import Admin -import peewee - - -def setup(): - app = Flask(__name__) - app.config['SECRET_KEY'] = '1' - app.config['CSRF_ENABLED'] = False - app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' - - db = peewee.SqliteDatabase(':memory:') - admin = Admin(app) - - return app, db, admin diff --git a/flask_admin/tests/peeweemodel/conftest.py b/flask_admin/tests/peeweemodel/conftest.py new file mode 100644 index 000000000..0cdeb91dc --- /dev/null +++ b/flask_admin/tests/peeweemodel/conftest.py @@ -0,0 +1,18 @@ +import pytest + +from flask_admin import Admin +import peewee + + +@pytest.fixture +def db(): + db = peewee.SqliteDatabase(':memory:') + yield db + + +@pytest.fixture +def admin(app, babel, db): + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + + admin = Admin(app) + yield admin diff --git a/flask_admin/tests/peeweemodel/test_basic.py b/flask_admin/tests/peeweemodel/test_basic.py index ea1ce8253..c4c0d96b6 100644 --- a/flask_admin/tests/peeweemodel/test_basic.py +++ b/flask_admin/tests/peeweemodel/test_basic.py @@ -10,8 +10,6 @@ from flask_admin._compat import iteritems from flask_admin.contrib.peewee import ModelView -from . import setup - from datetime import datetime, time, date @@ -104,8 +102,7 @@ def fill_db(Model1, Model2): Model1('datetime_obj2', datetime_field=datetime(2013, 3, 2, 0, 8, 0)).save() -def test_model(): - app, db, admin = setup() +def test_model(app, db, admin): Model1, Model2 = create_models(db) view = CustomModelView(Model1) @@ -177,9 +174,7 @@ def test_model(): # @pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") -def test_column_editable_list(): - app, db, admin = setup() - +def test_column_editable_list(app, db, admin): Model1, Model2 = create_models(db) # wtf-peewee doesn't automatically add length validators for max_length @@ -252,9 +247,7 @@ def test_column_editable_list(): assert 'test1_val_3' in data -def test_details_view(): - app, db, admin = setup() - +def test_details_view(app, db, admin): Model1, Model2 = create_models(db) view_no_details = CustomModelView(Model1) @@ -305,9 +298,7 @@ def test_details_view(): assert '5000' not in data -def test_column_filters(): - app, db, admin = setup() - +def test_column_filters(app, db, admin): Model1, Model2 = create_models(db) fill_db(Model1, Model2) @@ -855,8 +846,7 @@ def test_column_filters(): assert 'timeonly_obj2' in data -def test_default_sort(): - app, db, admin = setup() +def test_default_sort(app, db, admin): M1, _ = create_models(db) M1('c', 1).save() @@ -887,9 +877,7 @@ def test_default_sort(): assert data[2].test1 == 'a' -def test_extra_fields(): - app, db, admin = setup() - +def test_extra_fields(app, db, admin): Model1, _ = create_models(db) view = CustomModelView( @@ -913,9 +901,7 @@ def test_extra_fields(): assert pos2 < pos1 -def test_custom_form_base(): - app, db, admin = setup() - +def test_custom_form_base(app, db, admin): class TestForm(form.BaseForm): pass @@ -933,9 +919,7 @@ class TestForm(form.BaseForm): assert isinstance(create_form, TestForm) -def test_form_args(): - app, db, admin = setup() - +def test_form_args(app, db, admin): class BaseModel(peewee.Model): class Meta: database = db @@ -959,9 +943,7 @@ class Model(BaseModel): assert len(edit_form.test.validators) == 2 -def test_ajax_fk(): - app, db, admin = setup() - +def test_ajax_fk(app, db, admin): class BaseModel(peewee.Model): class Meta: database = db @@ -1038,9 +1020,7 @@ class Model2(BaseModel): assert mdl.model1.test1 == u'first' -def test_export_csv(): - app, db, admin = setup() - +def test_export_csv(app, db, admin): Model1, Model2 = create_models(db) view = CustomModelView(Model1, can_export=True, diff --git a/flask_admin/tests/pymongo/__init__.py b/flask_admin/tests/pymongo/__init__.py index 8a9d27b03..e69de29bb 100644 --- a/flask_admin/tests/pymongo/__init__.py +++ b/flask_admin/tests/pymongo/__init__.py @@ -1,17 +0,0 @@ -from pymongo import MongoClient - -from flask import Flask -from flask_admin import Admin - - -def setup(): - app = Flask(__name__) - app.config['SECRET_KEY'] = '1' - app.config['CSRF_ENABLED'] = False - - client = MongoClient() - db = client.tests - - admin = Admin(app) - - return app, db, admin diff --git a/flask_admin/tests/pymongo/conftest.py b/flask_admin/tests/pymongo/conftest.py new file mode 100644 index 000000000..eb3dc2062 --- /dev/null +++ b/flask_admin/tests/pymongo/conftest.py @@ -0,0 +1,17 @@ +import pytest +from pymongo import MongoClient + +from flask_admin import Admin + + +@pytest.fixture +def db(): + client = MongoClient() + db = client.tests + yield db + + +@pytest.fixture +def admin(app, babel, db): + admin = Admin(app) + yield admin diff --git a/flask_admin/tests/pymongo/test_basic.py b/flask_admin/tests/pymongo/test_basic.py index 267d0a0c3..7fe31335c 100644 --- a/flask_admin/tests/pymongo/test_basic.py +++ b/flask_admin/tests/pymongo/test_basic.py @@ -2,8 +2,6 @@ from flask_admin.contrib.pymongo import ModelView -from . import setup - class TestForm(form.Form): __test__ = False @@ -19,9 +17,7 @@ class TestView(ModelView): form = TestForm -def test_model(): - app, db, admin = setup() - +def test_model(app, db, admin): view = TestView(db.test, 'Test') admin.add_view(view) diff --git a/flask_admin/tests/sqla/__init__.py b/flask_admin/tests/sqla/__init__.py index c601abc50..e69de29bb 100644 --- a/flask_admin/tests/sqla/__init__.py +++ b/flask_admin/tests/sqla/__init__.py @@ -1,33 +0,0 @@ -from flask import Flask -from flask_admin import Admin -from flask_sqlalchemy import SQLAlchemy - - -def setup(): - app = Flask(__name__) - app.config['SECRET_KEY'] = '1' - app.config['CSRF_ENABLED'] = False - app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' - app.config['SQLALCHEMY_ECHO'] = True - app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False - - db = SQLAlchemy() - db.init_app(app) - admin = Admin(app) - - return app, db, admin - - -def setup_postgres(): - app = Flask(__name__) - app.config['SECRET_KEY'] = '1' - app.config['CSRF_ENABLED'] = False - app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:postgres@localhost/flask_admin_test' - app.config['SQLALCHEMY_ECHO'] = True - app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False - - db = SQLAlchemy() - db.init_app(app) - admin = Admin(app) - - return app, db, admin diff --git a/flask_admin/tests/sqla/conftest.py b/flask_admin/tests/sqla/conftest.py new file mode 100644 index 000000000..edeb0335f --- /dev/null +++ b/flask_admin/tests/sqla/conftest.py @@ -0,0 +1,48 @@ +import pytest +from flask import Flask + +from flask_admin import Admin +from flask_sqlalchemy import SQLAlchemy + + +@pytest.fixture(scope='function') +def app(): + # Overrides the `app` fixture in `flask_admin/tests/conftest.py` so that the `sqla` + # directory/import path is configured as the root path for Flask. This will + # cause the `templates` directory here to be used for template resolution. + app = Flask(__name__) + app.config['SECRET_KEY'] = '1' + app.config['WTF_CSRF_ENABLED'] = False + + yield app + + +@pytest.fixture +def db(app): + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['SQLALCHEMY_ECHO'] = True + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + db = SQLAlchemy(app) + yield db + + +@pytest.fixture +def postgres_db(app): + app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:postgres@localhost/flask_admin_test' + app.config['SQLALCHEMY_ECHO'] = True + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + + db = SQLAlchemy(app) + yield db + + +@pytest.fixture +def admin(app, babel, db): + admin = Admin(app) + yield admin + + +@pytest.fixture +def postgres_admin(app, babel, postgres_db): + admin = Admin(app) + yield admin diff --git a/flask_admin/tests/sqla/test_basic.py b/flask_admin/tests/sqla/test_basic.py index 97dd6bb7f..5a053cc18 100644 --- a/flask_admin/tests/sqla/test_basic.py +++ b/flask_admin/tests/sqla/test_basic.py @@ -7,12 +7,10 @@ from flask_admin._compat import as_unicode from flask_admin._compat import iteritems from flask_admin.contrib.sqla import ModelView, filters, tools -from flask_babel 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 @@ -156,9 +154,7 @@ def fill_db(db, Model1, Model2): @pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") -def test_model(): - app, db, admin = setup() - +def test_model(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -309,9 +305,7 @@ def test_model(): @pytest.mark.xfail(raises=Exception) -def test_no_pk(): - app, db, admin = setup() - +def test_no_pk(app, db, admin): class Model(db.Model): test = db.Column(db.Integer) @@ -319,9 +313,7 @@ class Model(db.Model): admin.add_view(view) -def test_list_columns(): - app, db, admin = setup() - +def test_list_columns(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -356,9 +348,7 @@ def test_list_columns(): assert 'Test2' not in data -def test_complex_list_columns(): - app, db, admin = setup() - +def test_complex_list_columns(app, db, admin): with app.app_context(): M1, M2 = create_models(db) @@ -381,9 +371,7 @@ def test_complex_list_columns(): assert 'model1_val1' in data -def test_exclude_columns(): - app, db, admin = setup() - +def test_exclude_columns(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -408,9 +396,7 @@ def test_exclude_columns(): assert 'Test2' not in data -def test_column_searchable_list(): - app, db, admin = setup() - +def test_column_searchable_list(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -443,9 +429,7 @@ def test_column_searchable_list(): assert 'model2-test' in data -def test_extra_args_search(): - app, db, admin = setup() - +def test_extra_args_search(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -465,9 +449,7 @@ def test_extra_args_search(): assert '' in data -def test_extra_args_filter(): - app, db, admin = setup() - +def test_extra_args_filter(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -487,9 +469,7 @@ def test_extra_args_filter(): @pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") -def test_complex_searchable_list(): - app, db, admin = setup() - +def test_complex_searchable_list(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -524,9 +504,7 @@ def test_complex_searchable_list(): @pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") -def test_complex_searchable_list_missing_children(): - app, db, admin = setup() - +def test_complex_searchable_list_missing_children(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -546,9 +524,7 @@ def test_complex_searchable_list_missing_children(): @pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") -def test_column_editable_list(): - app, db, admin = setup() - +def test_column_editable_list(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -617,9 +593,7 @@ def test_column_editable_list(): assert 'test1_val_3' in data -def test_details_view(): - app, db, admin = setup() - +def test_details_view(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -670,11 +644,9 @@ def test_details_view(): assert 'test1_val_1' not in data -def test_editable_list_special_pks(): +def test_editable_list_special_pks(app, db, admin): ''' 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): @@ -710,9 +682,7 @@ def __init__(self, id=None, val1=None): @pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") -def test_column_filters(): - app, db, admin = setup() - +def test_column_filters(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -1571,9 +1541,7 @@ def test_column_filters(): assert 'test1_val_2' not in data -def test_column_filters_sqla_obj(): - app, db, admin = setup() - +def test_column_filters_sqla_obj(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -1586,9 +1554,7 @@ def test_column_filters_sqla_obj(): assert len(view._filters) == 7 -def test_hybrid_property(): - app, db, admin = setup() - +def test_hybrid_property(app, db, admin): with app.app_context(): class Model1(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -1655,9 +1621,7 @@ def number_of_pixels_str(cls): assert 'test_row_1' not in data -def test_hybrid_property_nested(): - app, db, admin = setup() - +def test_hybrid_property_nested(app, db, admin): with app.app_context(): class Model1(db.Model): @@ -1704,9 +1668,7 @@ class Model2(db.Model): @pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") -def test_url_args(): - app, db, admin = setup() - +def test_url_args(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -1763,9 +1725,7 @@ def test_url_args(): assert 'data2' in data -def test_non_int_pk(): - app, db, admin = setup() - +def test_non_int_pk(app, db, admin): with app.app_context(): class Model(db.Model): @@ -1797,9 +1757,7 @@ class Model(db.Model): assert 'test2' in data -def test_form_columns(): - app, db, admin = setup() - +def test_form_columns(app, db, admin): with app.app_context(): class Model(db.Model): @@ -1863,9 +1821,7 @@ class EnumChoices(enum.Enum): @pytest.mark.xfail(raises=Exception) -def test_complex_form_columns(): - app, db, admin = setup() - +def test_complex_form_columns(app, db, admin): with app.app_context(): M1, M2 = create_models(db) @@ -1874,9 +1830,7 @@ def test_complex_form_columns(): view.create_form() -def test_form_args(): - app, db, admin = setup() - +def test_form_args(app, db, admin): with app.app_context(): class Model(db.Model): id = db.Column(db.String, primary_key=True) @@ -1897,9 +1851,7 @@ class Model(db.Model): assert len(edit_form.test.validators) == 2 -def test_form_override(): - app, db, admin = setup() - +def test_form_override(app, db, admin): with app.app_context(): class Model(db.Model): id = db.Column(db.String, primary_key=True) @@ -1916,9 +1868,7 @@ class Model(db.Model): assert view2._create_form_class.test.field_class == fields.FileField -def test_form_onetoone(): - app, db, admin = setup() - +def test_form_onetoone(app, db, admin): with app.app_context(): class Model1(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -1955,9 +1905,7 @@ def test_relations(): pass -def test_on_model_change_delete(): - app, db, admin = setup() - +def test_on_model_change_delete(app, db, admin): with app.app_context(): Model1, _ = create_models(db) @@ -1990,9 +1938,7 @@ def on_model_delete(self, model): assert view.deleted -def test_multiple_delete(): - app, db, admin = setup() - +def test_multiple_delete(app, db, admin): with app.app_context(): M1, _ = create_models(db) @@ -2010,9 +1956,7 @@ def test_multiple_delete(): assert M1.query.count() == 0 -def test_default_sort(): - app, db, admin = setup() - +def test_default_sort(app, db, admin): with app.app_context(): M1, _ = create_models(db) @@ -2068,9 +2012,7 @@ def test_default_sort(): assert data[2].test1 == 'a' -def test_complex_sort(): - app, db, admin = setup() - +def test_complex_sort(app, db, admin): with app.app_context(): M1, M2 = create_models(db) @@ -2121,9 +2063,7 @@ def test_complex_sort(): @pytest.mark.xfail(raises=Exception) -def test_complex_sort_exception(): - app, db, admin = setup() - +def test_complex_sort_exception(app, db, admin): with app.app_context(): M1, M2 = create_models(db) @@ -2140,9 +2080,7 @@ def test_complex_sort_exception(): assert data[1].model1.test1 == 'b' -def test_default_complex_sort(): - app, db, admin = setup() - +def test_default_complex_sort(app, db, admin): with app.app_context(): M1, M2 = create_models(db) @@ -2177,8 +2115,7 @@ def test_default_complex_sort(): assert data[1].model1.test1 == 'b' -def test_extra_fields(): - app, db, admin = setup() +def test_extra_fields(app, db, admin): with app.app_context(): Model1, _ = create_models(db) @@ -2203,9 +2140,7 @@ def test_extra_fields(): assert pos2 < pos1 -def test_extra_field_order(): - app, db, admin = setup() - +def test_extra_field_order(app, db, admin): with app.app_context(): Model1, _ = create_models(db) @@ -2230,45 +2165,59 @@ def test_extra_field_order(): assert pos2 > pos1 -def test_modelview_localization(): - def test_locale(locale): - try: - app, db, admin = setup() - - app.config['BABEL_DEFAULT_LOCALE'] = locale - Babel(app) +@pytest.mark.parametrize( + 'locale, expect_text', + ( + ('en', 'Home'), + ('cs', 'Domů'), + ('de', 'Start'), + ('es', 'Inicio'), + ('fa', 'خانه'), + ('fr', 'Accueil'), + ('pt', 'Início'), + ('ru', 'Главная'), + ('pa', 'ਹੋਮ'), + ('zh_CN', '首页'), + ('zh_TW', '首頁'), + ) +) +def test_modelview_localization(request, app, locale, expect_text): + # We need to configure the default Babel locale _before_ the `babel` fixture is + # initialised, so we have to use `request.getfixturevalue` to pull the fixture + # within the test function rather than the test signature. The `admin` fixture + # pulls in the `babel` fixture, which will then use the configuration here. + app.config['BABEL_DEFAULT_LOCALE'] = locale + db = request.getfixturevalue('db') + admin = request.getfixturevalue('admin') - 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) + with app.app_context(): + Model1, _ = create_models(db) - client = app.test_client() + view = CustomModelView( + Model1, db.session, + column_filters=['test1', 'bool_field', 'date_field', 'datetime_field', 'time_field'] + ) - rv = client.get('/admin/model1/') - assert rv.status_code == 200 + admin.add_view(view) - rv = client.get('/admin/model1/new/') - assert rv.status_code == 200 - except: - print("Error on the following locale:", locale) - raise + client = app.test_client() - locales = ['en', 'cs', 'de', 'es', 'fa', 'fr', 'pt', 'ru', 'zh_CN', 'zh_TW'] - for locale in locales: - test_locale(locale) + rv = client.get('/admin/model1/') + assert expect_text in rv.text + assert rv.status_code == 200 + rv = client.get('/admin/model1/new/') + assert rv.status_code == 200 -def test_modelview_named_filter_localization(): - app, db, admin = setup() +def test_modelview_named_filter_localization(request, app): + # We need to configure the default Babel locale _before_ the `babel` fixture is + # initialised, so we have to use `request.getfixturevalue` to pull the fixture + # within the test function rather than the test signature. The `admin` fixture + # pulls in the `babel` fixture, which will then use the configuration here. app.config['BABEL_DEFAULT_LOCALE'] = 'de' - Babel(app) + db = request.getfixturevalue('db') + _ = request.getfixturevalue('admin') with app.app_context(): Model1, _ = create_models(db) @@ -2286,9 +2235,7 @@ def test_modelview_named_filter_localization(): assert 'test1_equals' == flt_name -def test_custom_form_base(): - app, db, admin = setup() - +def test_custom_form_base(app, db, admin): with app.app_context(): class TestForm(form.BaseForm): pass @@ -2307,9 +2254,7 @@ class TestForm(form.BaseForm): assert isinstance(create_form, TestForm) -def test_ajax_fk(): - app, db, admin = setup() - +def test_ajax_fk(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) @@ -2372,9 +2317,7 @@ def test_ajax_fk(): assert mdl.model1.test1 == u'first' -def test_ajax_fk_multi(): - app, db, admin = setup() - +def test_ajax_fk_multi(app, db, admin): with app.app_context(): class Model1(db.Model): __tablename__ = 'model1' @@ -2439,9 +2382,7 @@ class Model2(db.Model): assert len(mdl.model1) == 1 -def test_safe_redirect(): - app, db, admin = setup() - +def test_safe_redirect(app, db, admin): with app.app_context(): Model1, _ = create_models(db) @@ -2477,8 +2418,7 @@ def test_safe_redirect(): assert 'id=2' in rv.location -def test_simple_list_pager(): - app, db, admin = setup() +def test_simple_list_pager(app, db, admin): with app.app_context(): Model1, _ = create_models(db) @@ -2495,8 +2435,7 @@ def get_count_query(self): assert count is None -def test_unlimited_page_size(): - app, db, admin = setup() +def test_unlimited_page_size(app, db, admin): with app.app_context(): M1, _ = create_models(db) @@ -2518,8 +2457,7 @@ def test_unlimited_page_size(): assert len(data) == 21 -def test_advanced_joins(): - app, db, admin = setup() +def test_advanced_joins(app, db, admin): with app.app_context(): class Model1(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -2593,8 +2531,7 @@ class Model3(db.Model): assert alias is None -def test_multipath_joins(): - app, db, admin = setup() +def test_multipath_joins(app, db, admin): with app.app_context(): class Model1(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -2624,8 +2561,7 @@ class Model2(db.Model): # TODO: Why this fails? @pytest.mark.xfail(raises=Exception) -def test_different_bind_joins(): - app, db, admin = setup() +def test_different_bind_joins(app, db, admin): app.config['SQLALCHEMY_BINDS'] = { 'other': 'sqlite:///' } @@ -2653,8 +2589,7 @@ class Model2(db.Model): assert rv.status_code == 200 -def test_model_default(): - app, db, admin = setup() +def test_model_default(app, db, admin): with app.app_context(): _, Model2 = create_models(db) @@ -2669,9 +2604,7 @@ class ModelView(CustomModelView): assert b'This field is required' not in rv.data -def test_export_csv(): - app, db, admin = setup() - +def test_export_csv(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) diff --git a/flask_admin/tests/sqla/test_form_rules.py b/flask_admin/tests/sqla/test_form_rules.py index faae6521b..75558240b 100644 --- a/flask_admin/tests/sqla/test_form_rules.py +++ b/flask_admin/tests/sqla/test_form_rules.py @@ -1,15 +1,12 @@ import pytest -from . import setup from .test_basic import CustomModelView, create_models from flask_admin.form import rules @pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") -def test_form_rules(): - app, db, admin = setup() - +def test_form_rules(app, db, admin): with app.app_context(): Model1, _ = create_models(db) db.create_all() @@ -34,9 +31,7 @@ def test_form_rules(): @pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") -def test_rule_macro(): - app, db, admin = setup() - +def test_rule_macro(app, db, admin): with app.app_context(): Model1, _ = create_models(db) db.create_all() @@ -58,9 +53,7 @@ def test_rule_macro(): @pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") -def test_rule_container(): - app, db, admin = setup() - +def test_rule_container(app, db, admin): with app.app_context(): Model1, _ = create_models(db) db.create_all() @@ -86,8 +79,7 @@ def test_rule_container(): @pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") -def test_rule_header(): - app, db, admin = setup() +def test_rule_header(app, db, admin): with app.app_context(): Model1, _ = create_models(db) db.create_all() @@ -106,8 +98,7 @@ def test_rule_header(): @pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") -def test_rule_field_set(): - app, db, admin = setup() +def test_rule_field_set(app, db, admin): with app.app_context(): Model1, _ = create_models(db) db.create_all() @@ -133,8 +124,7 @@ def test_rule_field_set(): @pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") -def test_rule_inlinefieldlist(): - app, db, admin = setup() +def test_rule_inlinefieldlist(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) db.create_all() @@ -150,8 +140,7 @@ def test_rule_inlinefieldlist(): assert rv.status_code == 200 -def test_inline_model_rules(): - app, db, admin = setup() +def test_inline_model_rules(app, db, admin): with app.app_context(): Model1, Model2 = create_models(db) db.create_all() diff --git a/flask_admin/tests/sqla/test_inlineform.py b/flask_admin/tests/sqla/test_inlineform.py index 8523b1f63..d28f89078 100644 --- a/flask_admin/tests/sqla/test_inlineform.py +++ b/flask_admin/tests/sqla/test_inlineform.py @@ -6,12 +6,8 @@ from flask_admin.contrib.sqla.fields import InlineModelFormList from flask_admin.contrib.sqla.validators import ItemsRequired -from . import setup - - -def test_inline_form(): - app, db, admin = setup() +def test_inline_form(app, db, admin): with app.app_context(): client = app.test_client() @@ -110,9 +106,7 @@ class UserModelView(ModelView): assert UserInfo.query.count() == 0 -def test_inline_form_required(): - app, db, admin = setup() - +def test_inline_form_required(app, db, admin): with app.app_context(): client = app.test_client() @@ -171,9 +165,7 @@ class UserModelView(ModelView): assert UserEmail.query.count() == 1 -def test_inline_form_ajax_fk(): - app, db, admin = setup() - +def test_inline_form_ajax_fk(app, db, admin): with app.app_context(): # Set up models and database class User(db.Model): @@ -228,9 +220,7 @@ class UserModelView(ModelView): assert 'userinfo-tag' in view._form_ajax_refs -def test_inline_form_self(): - app, db, admin = setup() - +def test_inline_form_self(app, db, admin): with app.app_context(): class Tree(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -250,8 +240,7 @@ class TreeView(ModelView): assert form.parent.data == parent -def test_inline_form_base_class(): - app, db, admin = setup() +def test_inline_form_base_class(app, db, admin): client = app.test_client() with app.app_context(): diff --git a/flask_admin/tests/sqla/test_multi_pk.py b/flask_admin/tests/sqla/test_multi_pk.py index dd122baee..59bfa9bee 100644 --- a/flask_admin/tests/sqla/test_multi_pk.py +++ b/flask_admin/tests/sqla/test_multi_pk.py @@ -1,14 +1,11 @@ -from . import setup from .test_basic import CustomModelView from flask_sqlalchemy import Model from sqlalchemy.ext.declarative import declarative_base -def test_multiple_pk(): +def test_multiple_pk(app, db, admin): # Test multiple primary keys - mix int and string together - app, db, admin = setup() - with app.app_context(): class Model(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -44,10 +41,8 @@ class Model(db.Model): assert rv.status_code == 302 -def test_joined_inheritance(): +def test_joined_inheritance(app, db, admin): # Test multiple primary keys - mix int and string together - app, db, admin = setup() - with app.app_context(): class Parent(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -84,10 +79,8 @@ class Child(Parent): assert 'bar' in data -def test_single_table_inheritance(): +def test_single_table_inheritance(app, db, admin): # Test multiple primary keys - mix int and string together - app, db, admin = setup() - with app.app_context(): CustomModel = declarative_base(cls=Model, name='Model') @@ -125,10 +118,8 @@ class Child(Parent): assert 'bar' in data -def test_concrete_table_inheritance(): +def test_concrete_table_inheritance(app, db, admin): # Test multiple primary keys - mix int and string together - app, db, admin = setup() - with app.app_context(): class Parent(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -161,10 +152,8 @@ class Child(Parent): assert 'bar' in data -def test_concrete_multipk_inheritance(): +def test_concrete_multipk_inheritance(app, db, admin): # Test multiple primary keys - mix int and string together - app, db, admin = setup() - with app.app_context(): class Parent(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/flask_admin/tests/sqla/test_postgres.py b/flask_admin/tests/sqla/test_postgres.py index 1cf202dd7..c374b6c43 100644 --- a/flask_admin/tests/sqla/test_postgres.py +++ b/flask_admin/tests/sqla/test_postgres.py @@ -1,22 +1,19 @@ -from . import setup_postgres from .test_basic import CustomModelView from sqlalchemy.dialects.postgresql import HSTORE, JSON from citext import CIText -def test_hstore(): - app, db, admin = setup_postgres() - +def test_hstore(app, postgres_db, postgres_admin): with app.app_context(): - class Model(db.Model): - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - hstore_test = db.Column(HSTORE) + class Model(postgres_db.Model): + id = postgres_db.Column(postgres_db.Integer, primary_key=True, autoincrement=True) + hstore_test = postgres_db.Column(HSTORE) - db.create_all() + postgres_db.create_all() - view = CustomModelView(Model, db.session) - admin.add_view(view) + view = CustomModelView(Model, postgres_db.session) + postgres_admin.add_view(view) client = app.test_client() @@ -42,18 +39,16 @@ class Model(db.Model): assert 'test_val2' in data -def test_json(): - app, db, admin = setup_postgres() - +def test_json(app, postgres_db, postgres_admin): with app.app_context(): - class JSONModel(db.Model): - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - json_test = db.Column(JSON) + class JSONModel(postgres_db.Model): + id = postgres_db.Column(postgres_db.Integer, primary_key=True, autoincrement=True) + json_test = postgres_db.Column(JSON) - db.create_all() + postgres_db.create_all() - view = CustomModelView(JSONModel, db.session) - admin.add_view(view) + view = CustomModelView(JSONModel, postgres_db.session) + postgres_admin.add_view(view) client = app.test_client() @@ -79,18 +74,17 @@ class JSONModel(db.Model): '{"test_key1": "test_value1"}<' in data) -def test_citext(): - app, db, admin = setup_postgres() +def test_citext(app, postgres_db, postgres_admin): with app.app_context(): - class CITextModel(db.Model): - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - citext_test = db.Column(CIText) + class CITextModel(postgres_db.Model): + id = postgres_db.Column(postgres_db.Integer, primary_key=True, autoincrement=True) + citext_test = postgres_db.Column(CIText) - db.engine.execute('CREATE EXTENSION IF NOT EXISTS citext') - db.create_all() + postgres_db.engine.execute('CREATE EXTENSION IF NOT EXISTS citext') + postgres_db.create_all() - view = CustomModelView(CITextModel, db.session) - admin.add_view(view) + view = CustomModelView(CITextModel, postgres_db.session) + postgres_admin.add_view(view) client = app.test_client() diff --git a/flask_admin/tests/sqla/test_translation.py b/flask_admin/tests/sqla/test_translation.py index ed1bc54ab..99150f1c4 100644 --- a/flask_admin/tests/sqla/test_translation.py +++ b/flask_admin/tests/sqla/test_translation.py @@ -1,19 +1,20 @@ from flask_admin.babel import lazy_gettext -from flask_babel import Babel -from . import setup from .test_basic import CustomModelView, create_models -def test_column_label_translation(): - app, db, admin = setup() +def test_column_label_translation(request, app): + # We need to configure the default Babel locale _before_ the `babel` fixture is + # initialised, so we have to use `request.getfixturevalue` to pull the fixture + # within the test function rather than the test signature. The `admin` fixture + # pulls in the `babel` fixture, which will then use the configuration here. + app.config['BABEL_DEFAULT_LOCALE'] = 'es' + db = request.getfixturevalue('db') + admin = request.getfixturevalue('admin') with app.app_context(): Model1, _ = create_models(db) - app.config['BABEL_DEFAULT_LOCALE'] = 'es' - Babel(app) - label = lazy_gettext('Name') view = CustomModelView(Model1, db.session, diff --git a/flask_admin/tests/test_base.py b/flask_admin/tests/test_base.py index 5ca3efb70..9c1616f25 100644 --- a/flask_admin/tests/test_base.py +++ b/flask_admin/tests/test_base.py @@ -8,6 +8,18 @@ from flask_admin import base +@pytest.fixture +def app(): + # Overrides the `app` fixture in `flask_admin/tests/conftest.py` so that the `sqla` + # directory/import path is configured as the root path for Flask. This will + # cause the `templates` directory here to be used for template resolution. + app = Flask(__name__) + app.config['SECRET_KEY'] = '1' + app.config['WTF_CSRF_ENABLED'] = False + + yield app + + class MockView(base.BaseView): # Various properties allow_call = True @@ -117,10 +129,9 @@ def test_custom_index_view(): assert admin._views[0] == view -def test_custom_index_view_in_init_app(): +def test_custom_index_view_in_init_app(app, babel): view = base.AdminIndexView(name='a', category='b', endpoint='c', url='/d', template='e') - app = Flask(__name__) admin = base.Admin() admin.init_app(app, index_view=view) @@ -136,16 +147,12 @@ def test_custom_index_view_in_init_app(): assert admin._views[0] == view -def test_base_registration(): - app = Flask(__name__) - admin = base.Admin(app) - +def test_base_registration(app, admin): assert admin.app == app assert admin.index_view.blueprint is not None -def test_admin_customizations(): - app = Flask(__name__) +def test_admin_customizations(app, babel): admin = base.Admin(app, name='Test', url='/foobar', static_url_path='/static/my/admin') assert admin.name == 'Test' assert admin.url == '/foobar' @@ -208,31 +215,21 @@ def test_baseview_registration(): @pytest.mark.filterwarnings("ignore:unclosed file:ResourceWarning") -def test_baseview_urls(): - app = Flask(__name__) - admin = base.Admin(app) - +def test_baseview_urls(admin): view = MockView() admin.add_view(view) assert len(view._urls) == 2 -# @pytest.mark.filterwarnings("ignore:unclosed file:ResourceWarning") -def test_add_views(): - app = Flask(__name__) - admin = base.Admin(app) - +def test_add_views(admin): admin.add_views(MockView(endpoint='test1'), MockView(endpoint='test2')) assert len(admin.menu()) == 3 -def test_add_category(): - app = Flask(__name__) - admin = base.Admin(app) - +def test_add_category(admin): admin.add_category('Category1', 'class-name', 'icon-type', 'icon-value') admin.add_view(MockView(name='Test 1', endpoint='test1', category='Category1')) admin.add_view(MockView(name='Test 2', endpoint='test2', category='Category2')) @@ -257,15 +254,11 @@ def test_add_category(): @pytest.mark.xfail(raises=Exception) -def test_no_default(): - app = Flask(__name__) - admin = base.Admin(app) +def test_no_default(admin): admin.add_view(base.BaseView()) -def test_call(): - app = Flask(__name__) - admin = base.Admin(app) +def test_call(app, admin): view = MockView() admin.add_view(view) client = app.test_client() @@ -285,9 +278,7 @@ def test_call(): assert rv.data == b'Failure!' -def test_permissions(): - app = Flask(__name__) - admin = base.Admin(app) +def test_permissions(app, admin): view = MockView() admin.add_view(view) client = app.test_client() @@ -298,9 +289,7 @@ def test_permissions(): assert rv.status_code == 403 -def test_inaccessible_callback(): - app = Flask(__name__) - admin = base.Admin(app) +def test_inaccessible_callback(app, admin): view = MockView() admin.add_view(view) client = app.test_client() @@ -312,10 +301,7 @@ def test_inaccessible_callback(): assert rv.status_code == 418 -def get_visibility(): - app = Flask(__name__) - admin = base.Admin(app) - +def get_visibility(app, admin): view = MockView(name='TestMenuItem') view.visible = False @@ -327,9 +313,7 @@ def get_visibility(): assert 'TestMenuItem' not in rv.data.decode('utf-8') -def test_submenu(): - app = Flask(__name__) - admin = base.Admin(app) +def test_submenu(admin): admin.add_view(MockView(name='Test 1', category='Test', endpoint='test1')) # Second view is not normally accessible @@ -354,11 +338,8 @@ def test_submenu(): assert children[0].is_accessible() -def test_delayed_init(): - app = Flask(__name__) - admin = base.Admin() +def test_delayed_init(app, admin): admin.add_view(MockView()) - admin.init_app(app) client = app.test_client() @@ -366,10 +347,7 @@ def test_delayed_init(): assert rv.data == b'Success!' -def test_multi_instances_init(): - app = Flask(__name__) - _ = base.Admin(app) - +def test_multi_instances_init(app, admin): class ManageIndex(base.AdminIndexView): pass @@ -377,16 +355,11 @@ class ManageIndex(base.AdminIndexView): @pytest.mark.xfail(raises=Exception) -def test_double_init(): - app = Flask(__name__) - admin = base.Admin(app) +def test_double_init(app, admin): admin.init_app(app) -def test_nested_flask_views(): - app = Flask(__name__) - admin = base.Admin(app) - +def test_nested_flask_views(app, admin): view = MockMethodView() admin.add_view(view) @@ -417,8 +390,7 @@ def test_nested_flask_views(): assert rv.data == b'GET - API3' -def test_root_mount(): - app = Flask(__name__) +def test_root_mount(app, babel): admin = base.Admin(app, url='/') admin.add_view(MockView()) @@ -433,9 +405,7 @@ def test_root_mount(): assert rv.status_code == 200 -def test_menu_links(): - app = Flask(__name__) - admin = base.Admin(app) +def test_menu_links(app, admin): admin.add_link(base.MenuLink('TestMenuLink1', endpoint='.index')) admin.add_link(base.MenuLink('TestMenuLink2', url='http://python.org/')) @@ -447,9 +417,7 @@ def test_menu_links(): assert 'TestMenuLink2' in data -def test_add_links(): - app = Flask(__name__) - admin = base.Admin(app) +def test_add_links(app, admin): admin.add_links(base.MenuLink('TestMenuLink1', endpoint='.index'), base.MenuLink('TestMenuLink2', url='http://python.org/')) diff --git a/flask_admin/tests/test_form_upload.py b/flask_admin/tests/test_form_upload.py index 2faeb03d5..73c4ee858 100644 --- a/flask_admin/tests/test_form_upload.py +++ b/flask_admin/tests/test_form_upload.py @@ -4,6 +4,7 @@ from io import BytesIO from flask import Flask, url_for + from flask_admin import form, helpers @@ -26,9 +27,7 @@ def safe_delete(path, name): pass -def test_upload_field(): - app = Flask(__name__) - +def test_upload_field(app, babel): path = _create_temp() def _remove_testfiles(): diff --git a/flask_admin/tests/test_model.py b/flask_admin/tests/test_model.py index d4fd8b1bb..0197573db 100644 --- a/flask_admin/tests/test_model.py +++ b/flask_admin/tests/test_model.py @@ -109,18 +109,7 @@ def delete_model(self, model): return True -def setup(): - app = Flask(__name__) - app.config['CSRF_ENABLED'] = False - app.secret_key = '1' - admin = Admin(app) - - return app, admin - - -def test_mockview(): - app, admin = setup() - +def test_mockview(app, admin): view = MockModelView(Model) admin.add_view(view) @@ -205,9 +194,7 @@ def test_mockview(): assert model.col1 == 'another test!' -def test_permissions(): - app, admin = setup() - +def test_permissions(app, admin): view = MockModelView(Model) admin.add_view(view) @@ -226,9 +213,7 @@ def test_permissions(): assert rv.status_code == 302 -def test_templates(): - app, admin = setup() - +def test_templates(app, admin): view = MockModelView(Model) admin.add_view(view) @@ -248,9 +233,7 @@ def test_templates(): assert rv.data == b'Success!' -def test_list_columns(): - app, admin = setup() - +def test_list_columns(app, admin): view = MockModelView(Model, column_list=['col1', 'col3'], column_labels=dict(col1='Column1')) @@ -267,9 +250,7 @@ def test_list_columns(): assert 'Col2' not in data -def test_exclude_columns(): - app, admin = setup() - +def test_exclude_columns(app, admin): view = MockModelView(Model, column_exclude_list=['col2']) admin.add_view(view) @@ -283,18 +264,14 @@ def test_exclude_columns(): assert 'Col2' not in data -def test_sortable_columns(): - app, admin = setup() - +def test_sortable_columns(app, admin): view = MockModelView(Model, column_sortable_list=['col1', ('col2', 'test1')]) admin.add_view(view) assert view._sortable_columns == dict(col1='col1', col2='test1') -def test_column_searchable_list(): - app, admin = setup() - +def test_column_searchable_list(app, admin): view = MockModelView(Model, column_searchable_list=['col1', 'col2']) admin.add_view(view) @@ -303,9 +280,7 @@ def test_column_searchable_list(): # TODO: Make calls with search -def test_column_filters(): - app, admin = setup() - +def test_column_filters(app, admin): view = MockModelView(Model, column_filters=['col1', 'col2']) admin.add_view(view) @@ -319,9 +294,7 @@ def test_column_filters(): # TODO: Make calls with filters -def test_filter_list_callable(): - app, admin = setup() - +def test_filter_list_callable(app, admin): flt = SimpleFilter('test', options=lambda: [('1', 'Test 1'), ('2', 'Test 2')]) view = MockModelView(Model, column_filters=[flt]) @@ -340,7 +313,7 @@ def test_form(): pass -def test_csrf(): +def test_csrf(app, admin): class SecureModelView(MockModelView): form_base_class = form.SecureForm @@ -352,8 +325,6 @@ def get_csrf_token(data): token = data.split('"')[0] return token - app, admin = setup() - view = SecureModelView(Model, endpoint='secure') admin.add_view(view) @@ -437,9 +408,7 @@ def get_csrf_token(data): assert u'Failed to perform action.' in rv.data.decode('utf-8') -def test_custom_form(): - app, admin = setup() - +def test_custom_form(app, admin): class TestForm(form.BaseForm): pass @@ -452,25 +421,19 @@ class TestForm(form.BaseForm): assert not hasattr(view._create_form_class, 'col1') -def test_modal_edit(): - # bootstrap 2 - test edit_modal - app_bs2 = Flask(__name__) - admin_bs2 = Admin(app_bs2, template_mode="bootstrap2") +def test_modal_edit_bs2(app, babel): + admin_bs2 = Admin(app, template_mode="bootstrap2") - edit_modal_on = MockModelView(Model, edit_modal=True, - endpoint="edit_modal_on") - edit_modal_off = MockModelView(Model, edit_modal=False, - endpoint="edit_modal_off") - create_modal_on = MockModelView(Model, create_modal=True, - endpoint="create_modal_on") - create_modal_off = MockModelView(Model, create_modal=False, - endpoint="create_modal_off") + edit_modal_on = MockModelView(Model, edit_modal=True, endpoint="edit_modal_on") + edit_modal_off = MockModelView(Model, edit_modal=False, endpoint="edit_modal_off") + create_modal_on = MockModelView(Model, create_modal=True, endpoint="create_modal_on") + create_modal_off = MockModelView(Model, create_modal=False, endpoint="create_modal_off") admin_bs2.add_view(edit_modal_on) admin_bs2.add_view(edit_modal_off) admin_bs2.add_view(create_modal_on) admin_bs2.add_view(create_modal_off) - client_bs2 = app_bs2.test_client() + client_bs2 = app.test_client() # bootstrap 2 - ensure modal window is added when edit_modal is enabled rv = client_bs2.get('/admin/edit_modal_on/') @@ -496,16 +459,20 @@ def test_modal_edit(): data = rv.data.decode('utf-8') assert 'fa_modal_window' not in data - # bootstrap 3 - app_bs3 = Flask(__name__) - admin_bs3 = Admin(app_bs3, template_mode="bootstrap3") +def test_modal_edit_bs3(app, babel): + admin_bs3 = Admin(app, template_mode="bootstrap3") + + edit_modal_on = MockModelView(Model, edit_modal=True, endpoint="edit_modal_on") + edit_modal_off = MockModelView(Model, edit_modal=False, endpoint="edit_modal_off") + create_modal_on = MockModelView(Model, create_modal=True, endpoint="create_modal_on") + create_modal_off = MockModelView(Model, create_modal=False, endpoint="create_modal_off") admin_bs3.add_view(edit_modal_on) admin_bs3.add_view(edit_modal_off) admin_bs3.add_view(create_modal_on) admin_bs3.add_view(create_modal_off) - client_bs3 = app_bs3.test_client() + client_bs3 = app.test_client() # bootstrap 3 - ensure modal window is added when edit_modal is enabled rv = client_bs3.get('/admin/edit_modal_on/') @@ -540,8 +507,7 @@ class DummyView(MockModelView): assert view.name == 'Dummy View' -def test_export_csv(): - app, admin = setup() +def test_export_csv(app, admin): client = app.test_client() # test redirect when csv export is disabled @@ -740,8 +706,7 @@ def export_formatter(v, c, m, p): assert rv.status_code == 500 -def test_list_row_actions(): - app, admin = setup() +def test_list_row_actions(app, admin): client = app.test_client() from flask_admin.model import template