diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..2ff985a67 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +charset = utf-8 +max_line_length = 88 + +[*.{css,html,js,json,jsx,scss,ts,tsx,yaml,yml}] +indent_size = 2 diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 000000000..f1377a89c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,29 @@ +--- +name: Bug report +about: Report a bug in Flask-Admin (not other projects which depend on Flask-Admin) +--- + + + + + + + +Environment: + +- Python version: +- Flask version: +- Flask-Admin version: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..4e64f3d22 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Questions on Discussions + url: https://github.com/pallets-eco/flask-admin/discussions/ + about: Ask questions about your own code on the Discussions tab. + - name: Questions on Chat + url: https://discord.gg/pallets + about: Ask questions about your own code on our Discord chat. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 000000000..39c8f0875 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,15 @@ +--- +name: Feature request +about: Suggest a new feature for Flask-Admin +--- + + + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..1f47f125e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + groups: + github-actions: + patterns: + - '*' + - package-ecosystem: pip + directory: /requirements/ + schedule: + interval: monthly + groups: + python-requirements: + patterns: + - '*' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..dbb67e35f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,25 @@ + + + + + diff --git a/.github/workflows/lock.yaml b/.github/workflows/lock.yaml new file mode 100644 index 000000000..22228a1cd --- /dev/null +++ b/.github/workflows/lock.yaml @@ -0,0 +1,23 @@ +name: Lock inactive closed issues +# Lock closed issues that have not received any further activity for two weeks. +# This does not close open issues, only humans may do that. It is easier to +# respond to new issues with fresh examples rather than continuing discussions +# on old issues. + +on: + schedule: + - cron: '0 0 * * *' +permissions: + issues: write + pull-requests: write +concurrency: + group: lock +jobs: + lock: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 + with: + issue-inactive-days: 14 + pr-inactive-days: 14 + discussion-inactive-days: 14 diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 000000000..6d496bf65 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,73 @@ +name: Publish +on: + push: + tags: + - '*' +jobs: + build: + runs-on: ubuntu-latest + outputs: + hash: ${{ steps.hash.outputs.hash }} + steps: + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: '3.x' + cache: pip + cache-dependency-path: requirements*/*.txt + - run: pip install -r requirements/build.txt + # Use the commit date instead of the current date during the build. + - run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV + - run: python -m build + # Generate hashes used for provenance. + - name: generate hash + id: hash + run: cd dist && echo "hash=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + path: ./dist + provenance: + needs: [build] + permissions: + actions: read + id-token: write + contents: write + # Can't pin with hash due to how this workflow works. + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 + with: + base64-subjects: ${{ needs.build.outputs.hash }} + create-release: + # Upload the sdist, wheels, and provenance to a GitHub release. They remain + # available as build artifacts for a while as well. + needs: [provenance] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + - name: create release + run: > + gh release create --draft --repo ${{ github.repository }} + ${{ github.ref_name }} + *.intoto.jsonl/* artifact/* + env: + GH_TOKEN: ${{ github.token }} + publish-pypi: + needs: [provenance] + # Wait for approval before attempting to upload to PyPI. This allows reviewing the + # files in the draft release. + environment: + name: publish + url: https://pypi.org/project/Flask-Admin/${{ github.ref_name }} + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + - uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14 + with: + repository-url: https://test.pypi.org/legacy/ + packages-dir: artifact/ + - uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14 + with: + packages-dir: artifact/ diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml deleted file mode 100644 index 0545bd528..000000000 --- a/.github/workflows/test.yaml +++ /dev/null @@ -1,67 +0,0 @@ -name: Run tests -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - test-job: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] - tox-version: [ 'WTForms2' ] - include: - - python-version: 3.11 - tox-version: flake8 - - python-version: 3.11 - 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:5.0.14-focal - ports: - - 27017:27017 - azurite: - image: arafato/azurite:2.6.5 - env: - executable: blob - ports: - - 10000:10000 - steps: - - name: Install Ubuntu packages - run: | - sudo apt-get update - sudo apt-get install -y libgeos-c1v5 - - name: Check out repository code - uses: actions/checkout@v3 - - name: Set up PostgreSQL hstore module - env: - PGPASSWORD: postgres - run: psql -U postgres -h localhost -c 'CREATE EXTENSION hstore;' flask_admin_test - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install tox - run: pip install tox - - name: Run tests - run: tox -e ${{ matrix.tox-version }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 000000000..5c5bf7515 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,92 @@ +name: Tests +on: + push: + branches: + - master + - '*.x' + paths-ignore: + - 'docs/**' + - '*.md' + - '*.rst' + pull_request: + paths-ignore: + - 'docs/**' + - '*.md' + - '*.rst' +jobs: + tests: + name: ${{ format('py{0}-wtforms{1}', matrix.python, matrix.wtforms) }} + runs-on: ${{ matrix.os || 'ubuntu-latest' }} + strategy: + fail-fast: false + matrix: + python: ['3.7', '3.8', '3.9', '3.10', '3.11'] + wtforms: ['2'] + 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:5.0.14-focal + ports: + - 27017:27017 + azurite: + image: arafato/azurite:2.6.5 + env: + executable: blob + ports: + - 10000:10000 + steps: + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: ${{ matrix.python }} + allow-prereleases: true + cache: pip + cache-dependency-path: requirements*/*.txt + - name: Install Ubuntu packages + run: | + sudo apt-get update + sudo apt-get install -y libgeos-c1v5 + - name: Check out repository code + uses: actions/checkout@v3 + - name: Set up PostgreSQL hstore module + env: + PGPASSWORD: postgres + run: psql -U postgres -h localhost -c 'CREATE EXTENSION hstore;' flask_admin_test + - run: pip install tox + - run: tox run -e ${{ matrix.tox || format('py{0}-wtforms{1}', matrix.python, matrix.wtforms) }} + not_tests: + name: ${{ matrix.tox }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + tox: ['docs', 'typing'] + steps: + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: '3.x' + cache: pip + cache-dependency-path: requirements*/*.txt + - name: cache mypy + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + path: ./.mypy_cache + key: mypy|${{ hashFiles('pyproject.toml') }} + - run: pip install tox + - run: tox run -e ${{ matrix.tox }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..0b0bdf927 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +ci: + autoupdate_schedule: monthly +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.7 + hooks: + - id: ruff + - id: ruff-format + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-merge-conflict + - id: debug-statements + - id: fix-byte-order-marker + - id: trailing-whitespace + - id: end-of-file-fixer diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..49c6e5e2f --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 +build: + os: ubuntu-22.04 + tools: + python: '3.11' +python: + install: + - requirements: requirements/docs.txt + - method: pip + path: . +sphinx: + builder: dirhtml + fail_on_warning: true diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..c76f9036c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,24 @@ +Copyright 2011 Pallets Community Ecosystem + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index ac26d4a7c..4f1b839e6 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ by doing: git clone git@github.com:pallets-eco/flask-admin.git cd flask-admin - python setup.py install + pip install . ## Tests @@ -105,8 +105,7 @@ Test are run with *pytest*. If you are not familiar with this package you can get some more info from [their website](https://pytest.org/). To run the tests, from the project directory, simply: - - pip install -r requirements-dev.txt + pip install --use-pep517 -r requirements/dev.txt pytest You should see output similar to: @@ -117,8 +116,11 @@ You should see output similar to: OK -For all the tests to pass successfully, you\'ll need Postgres & MongoDB -to be running locally. For Postgres: +*NOTE*: For all the tests to pass successfully, you\'ll need Postgres (with +the postgis and hstore extension) & MongoDB to be running locally. You'll +also need libgeos available. + +For Postgres: > psql postgres CREATE DATABASE flask_admin_test; @@ -130,8 +132,10 @@ to be running locally. For Postgres: If you\'re using Homebrew on MacOS, you might need this: - # install postgis + # install postgis and geos > brew install postgis + > brew install geos + > export DYLD_LIBRARY_PATH=/opt/homebrew/opt/geos/lib/ # set up postgresql user > createuser -s postgresql diff --git a/flask_admin/__init__.py b/flask_admin/__init__.py index ee6b8767f..a074a308c 100644 --- a/flask_admin/__init__.py +++ b/flask_admin/__init__.py @@ -1,6 +1,6 @@ __version__ = '1.6.1' __author__ = 'Flask-Admin team' -__email__ = 'serge.koval+github@gmail.com' +__email__ = 'contact@palletsproject.com' from .base import expose, expose_plugview, Admin, BaseView, AdminIndexView # noqa: F401 diff --git a/flask_admin/contrib/__init__.py b/flask_admin/contrib/__init__.py index 42e33a76c..24608e116 100644 --- a/flask_admin/contrib/__init__.py +++ b/flask_admin/contrib/__init__.py @@ -1,4 +1,4 @@ try: - __import__('pkg_resources').declare_namespace(__name__) + __path__ = __import__('pkgutil').extend_path(__path__, __name__) except ImportError: pass diff --git a/flask_admin/tests/geoa/test_basic.py b/flask_admin/tests/geoa/test_basic.py index 11c7fa602..7009107b2 100644 --- a/flask_admin/tests/geoa/test_basic.py +++ b/flask_admin/tests/geoa/test_basic.py @@ -2,6 +2,7 @@ import json import re +import pytest from flask_admin.contrib.geoa import ModelView from flask_admin.contrib.geoa.fields import GeoJSONField @@ -26,6 +27,7 @@ def __unicode__(self): return GeoModel +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_model(): app, db, admin = setup() GeoModel = create_models(db) diff --git a/flask_admin/tests/mongoengine/test_basic.py b/flask_admin/tests/mongoengine/test_basic.py index b977d23b5..20fbb61ab 100644 --- a/flask_admin/tests/mongoengine/test_basic.py +++ b/flask_admin/tests/mongoengine/test_basic.py @@ -1,3 +1,4 @@ +import pytest from wtforms import fields, validators from flask_admin import form diff --git a/flask_admin/tests/peeweemodel/test_basic.py b/flask_admin/tests/peeweemodel/test_basic.py index fa7f97645..ea1ce8253 100644 --- a/flask_admin/tests/peeweemodel/test_basic.py +++ b/flask_admin/tests/peeweemodel/test_basic.py @@ -1,3 +1,5 @@ +import pytest + from flask_admin._compat import as_unicode import peewee @@ -174,6 +176,7 @@ def test_model(): assert Model1.select().count() == 0 +# @pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_column_editable_list(): app, db, admin = setup() diff --git a/flask_admin/tests/sqla/test_basic.py b/flask_admin/tests/sqla/test_basic.py index 981903f4a..65bd3d0cf 100644 --- a/flask_admin/tests/sqla/test_basic.py +++ b/flask_admin/tests/sqla/test_basic.py @@ -155,6 +155,7 @@ def fill_db(db, Model1, Model2): db.session.commit() +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_model(): app, db, admin = setup() @@ -485,6 +486,7 @@ def test_extra_args_filter(): assert '' in data +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_complex_searchable_list(): app, db, admin = setup() @@ -521,6 +523,7 @@ def test_complex_searchable_list(): assert 'model1-test2-val' not in data +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_complex_searchable_list_missing_children(): app, db, admin = setup() @@ -542,6 +545,7 @@ def test_complex_searchable_list_missing_children(): assert 'magic string' in data +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_column_editable_list(): app, db, admin = setup() @@ -705,6 +709,7 @@ def __init__(self, id=None, val1=None): assert 'change-success-1' in data +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_column_filters(): app, db, admin = setup() @@ -1698,6 +1703,7 @@ class Model2(db.Model): assert 'Jim Smith' in data +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_url_args(): app, db, admin = setup() diff --git a/flask_admin/tests/sqla/test_form_rules.py b/flask_admin/tests/sqla/test_form_rules.py index 86959ee0a..faae6521b 100644 --- a/flask_admin/tests/sqla/test_form_rules.py +++ b/flask_admin/tests/sqla/test_form_rules.py @@ -1,9 +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() @@ -30,6 +33,7 @@ def test_form_rules(): assert pos3 == -1 +@pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") def test_rule_macro(): app, db, admin = setup() @@ -53,6 +57,7 @@ def test_rule_macro(): assert 'Hello another_test' in data +@pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") def test_rule_container(): app, db, admin = setup() @@ -80,6 +85,7 @@ def test_rule_container(): assert pos1 < pos2 < pos3 +@pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") def test_rule_header(): app, db, admin = setup() with app.app_context(): @@ -99,6 +105,7 @@ def test_rule_header(): assert '