Repository: flask-admin/flask-admin

Issue:
Migrate to Github Actions
Following up the discussion #2153,  I'll be this issue to track the migration to Github actions and fixing of the unit tests.


Discussion:

Resolving patch:
diff --git a/flask_admin/contrib/sqla/form.py b/flask_admin/contrib/sqla/form.py
--- a/flask_admin/contrib/sqla/form.py
+++ b/flask_admin/contrib/sqla/form.py
@@ -282,7 +282,7 @@ def conv_String(self, column, field_args, **extra):
     @converts('sqlalchemy.sql.sqltypes.Enum')
     def convert_enum(self, column, field_args, **extra):
         available_choices = [(f, f) for f in column.type.enums]
-        accepted_values = [key for key, val in available_choices]
+        accepted_values = [choice[0] for choice in available_choices]
 
         if column.nullable:
             field_args['allow_blank'] = column.nullable
@@ -304,7 +304,7 @@ def convert_choice_type(self, column, field_args, **extra):
             available_choices = [(f.value, f.name) for f in column.type.choices]
         else:
             available_choices = column.type.choices
-        accepted_values = [key for key, val in available_choices]
+        accepted_values = [choice[0] if isinstance(choice, tuple) else choice.value for choice in available_choices]
 
         if column.nullable:
             field_args['allow_blank'] = column.nullable
diff --git a/flask_admin/form/fields.py b/flask_admin/form/fields.py
--- a/flask_admin/form/fields.py
+++ b/flask_admin/form/fields.py
@@ -121,8 +121,11 @@ def iter_choices(self):
         if self.allow_blank:
             yield (u'__None', self.blank_text, self.data is None)
 
-        for value, label in self.choices:
-            yield (value, label, self.coerce(value) == self.data)
+        for choice in self.choices:
+            if isinstance(choice, tuple):
+                yield (choice[0], choice[1], self.coerce(choice[0]) == self.data)
+            else:
+                yield (choice.value, choice.name, self.coerce(choice.value) == self.data)
 
     def process_data(self, value):
         if value is None:


Test patch:
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,62 @@
+name: Run tests
+on:
+  - pull_request
+  - push
+
+jobs:
+  test-job:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
+        tox-version: [ 'WTForms2' ]
+        include:
+          - python-version: 3.6
+            tox-version: flake8
+          - python-version: 3.6
+            tox-version: docs-html
+    services:
+      # Label used to access the service container
+      postgres:
+        # Docker Hub image
+        image: postgis/postgis:12-master # postgres with postgis installed
+        # Provide the password for postgres
+        env:
+          POSTGRES_PASSWORD: postgres
+          POSTGRES_DB: flask_admin_test
+        ports:
+          - 5432:5432
+          # Set health checks to wait until postgres has started
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+      mongo:
+        image: mongo:3.1
+        ports:
+          - 27017:27017
+      azurite:
+        image: arafato/azurite:2.6.5
+        env:
+          executable: blob
+        ports:
+          - 10000:10000
+    steps:
+      # Downloads a copy of the code in your repository before running CI tests
+      - name: Check out repository code
+        uses: actions/checkout@v2
+      - name: Install postgis
+        run: sudo apt-get install -y postgis postgresql-12-postgis-3 postgresql-12-postgis-3-scripts
+      - name: Setup postgis
+        env:
+          PGPASSWORD: postgres
+        run: psql -U postgres -h localhost -c 'CREATE EXTENSION hstore;' flask_admin_test
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install tox
+        run: pip install tox
+      - name: Run tests
+        run: tox -e ${{ matrix.tox-version }}
\ No newline at end of file
diff --git a/flask_admin/tests/geoa/__init__.py b/flask_admin/tests/geoa/__init__.py
--- a/flask_admin/tests/geoa/__init__.py
+++ b/flask_admin/tests/geoa/__init__.py
@@ -7,7 +7,7 @@ def setup():
     app = Flask(__name__)
     app.config['SECRET_KEY'] = '1'
     app.config['CSRF_ENABLED'] = False
-    app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://localhost/flask_admin_test'
+    app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:postgres@localhost/flask_admin_test'
     app.config['SQLALCHEMY_ECHO'] = True
     app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
 
diff --git a/flask_admin/tests/geoa/test_basic.py b/flask_admin/tests/geoa/test_basic.py
--- a/flask_admin/tests/geoa/test_basic.py
+++ b/flask_admin/tests/geoa/test_basic.py
@@ -2,6 +2,7 @@
 import json
 import re
 
+
 from flask_admin.contrib.geoa import ModelView
 from flask_admin.contrib.geoa.fields import GeoJSONField
 from geoalchemy2 import Geometry
@@ -74,14 +75,14 @@ def test_model():
     assert to_shape(model.point).geom_type == "Point"
     assert list(to_shape(model.point).coords) == [(125.8, 10.0,)]
     assert to_shape(model.line).geom_type == "LineString"
-    assert list(to_shape(model.line).coords) == [(50.2345, 94.2, (50.21, 94.87)])
+    assert list(to_shape(model.line).coords) == [(50.2345, 94.2), (50.21, 94.87)]
     assert to_shape(model.polygon).geom_type == "Polygon"
-    eq_(list(to_shape(model.polygon).exterior.coords),
-        [(100.0, 0.0), (101.0, 0.0), (101.0, 1.0), (100.0, 1.0), (100.0, 0.0)])
+    assert list(to_shape(model.polygon).exterior.coords) == \
+           [(100.0, 0.0), (101.0, 0.0), (101.0, 1.0), (100.0, 1.0), (100.0, 0.0)]
     assert to_shape(model.multi).geom_type == "MultiPoint"
     assert len(to_shape(model.multi).geoms) == 2
-    assert list(to_shape(model.multi).geoms[0].coords) == [(100.0, 0.0])
-    assert list(to_shape(model.multi).geoms[1].coords) == [(101.0, 1.0])
+    assert list(to_shape(model.multi).geoms[0].coords) == [(100.0, 0.0)]
+    assert list(to_shape(model.multi).geoms[1].coords) == [(101.0, 1.0)]
 
     rv = client.get('/admin/geomodel/')
     assert rv.status_code == 200
diff --git a/flask_admin/tests/sqla/__init__.py b/flask_admin/tests/sqla/__init__.py
--- a/flask_admin/tests/sqla/__init__.py
+++ b/flask_admin/tests/sqla/__init__.py
@@ -21,7 +21,7 @@ def setup_postgres():
     app = Flask(__name__)
     app.config['SECRET_KEY'] = '1'
     app.config['CSRF_ENABLED'] = False
-    app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://localhost/flask_admin_test'
+    app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:postgres@localhost/flask_admin_test'
     app.config['SQLALCHEMY_ECHO'] = True
     app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
 
diff --git a/flask_admin/tests/test_base.py b/flask_admin/tests/test_base.py
--- a/flask_admin/tests/test_base.py
+++ b/flask_admin/tests/test_base.py
@@ -4,6 +4,7 @@
 
 from flask import Flask, request, abort, url_for
 from flask.views import MethodView
+
 from flask_admin import base
 
 
@@ -233,23 +234,23 @@ def test_add_category():
     admin.add_view(MockView(name='Test 1', endpoint='test1', category='Category1'))
     admin.add_view(MockView(name='Test 2', endpoint='test2', category='Category2'))
 
-    eq_(len(admin.menu()), 3)
+    assert len(admin.menu()) == 3
 
     # Test 1 should be underneath Category1
-    eq_(admin.menu()[1].name, 'Category1')
-    eq_(admin.menu()[1].get_class_name(), 'class-name')
-    eq_(admin.menu()[1].get_icon_type(), 'icon-type')
-    eq_(admin.menu()[1].get_icon_value(), 'icon-value')
-    eq_(len(admin.menu()[1].get_children()), 1)
-    eq_(admin.menu()[1].get_children()[0].name, 'Test 1')
+    assert admin.menu()[1].name == 'Category1'
+    assert admin.menu()[1].get_class_name() == 'class-name'
+    assert admin.menu()[1].get_icon_type() == 'icon-type'
+    assert admin.menu()[1].get_icon_value() == 'icon-value'
+    assert len(admin.menu()[1].get_children()) == 1
+    assert admin.menu()[1].get_children()[0].name == 'Test 1'
 
     # Test 2 should be underneath Category2
-    eq_(admin.menu()[2].name, 'Category2')
-    eq_(admin.menu()[2].get_class_name(), None)
-    eq_(admin.menu()[2].get_icon_type(), None)
-    eq_(admin.menu()[2].get_icon_value(), None)
-    eq_(len(admin.menu()[2].get_children()), 1)
-    eq_(admin.menu()[2].get_children()[0].name, 'Test 2')
+    assert admin.menu()[2].name == 'Category2'
+    assert admin.menu()[2].get_class_name() == None
+    assert admin.menu()[2].get_icon_type() == None
+    assert admin.menu()[2].get_icon_value() == None
+    assert len(admin.menu()[2].get_children()) == 1
+    assert admin.menu()[2].get_children()[0].name == 'Test 2'
 
 
 @pytest.mark.xfail(raises=Exception)