Repository: maykinmedia/django-admin-index (language: python)

Issue:
Clean up package/repo structure
This should be brought in line with our default app template: https://github.com/maykinmedia/default-app


Discussion / hints:

Fix patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index f1b88e6..277ff11 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -2,12 +2,8 @@
 commit = False
 tag = False
 current_version = 1.6.0
-parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.dev(?P<dev>\d+))?
-serialize = 
-	{major}.{minor}.{patch}.dev{dev}
-	{major}.{minor}.{patch}
 
-[bumpversion:part:dev]
+[bumpversion:file:setup.cfg]
 
 [bumpversion:file:README.rst]
 search = :Version: {current_version}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 77f14d0..8a970fb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,7 +31,7 @@ jobs:
           python-version: ${{ matrix.python }}
 
       - name: Install dependencies
-        run: pip install tox codecov
+        run: pip install tox tox-gh-actions codecov
 
       - name: Run tests
         run: |
@@ -44,4 +44,27 @@ jobs:
 
       - name: Publish coverage report
         uses: codecov/codecov-action@v2
-        if: matrix.python == '3.8' && matrix.django == '3.2'
+
+  publish:
+    name: Publish package to PyPI
+    runs-on: ubuntu-latest
+    needs: tests
+
+    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
+
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-python@v2
+        with:
+          python-version: '3.9'
+
+      - name: Build sdist and wheel
+        run: |
+          pip install pip setuptools wheel --upgrade
+          python setup.py sdist bdist_wheel
+
+      - name: Publish a Python distribution to PyPI
+        uses: pypa/gh-action-pypi-publish@release/v1
+        with:
+          user: ${{ secrets.PYPI_USERNAME }}
+          password: ${{ secrets.PYPI_TOKEN }}
diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml
new file mode 100644
index 0000000..cbb6b92
--- /dev/null
+++ b/.github/workflows/code_quality.yml
@@ -0,0 +1,35 @@
+name: Code quality checks
+
+# Run this workflow every time a new commit pushed to your repository
+on:
+  push:
+    branches:
+      - master
+    tags:
+      - '*'
+    paths:
+      - '**.py'
+  pull_request:
+    paths:
+      - '**.py'
+  workflow_dispatch:
+
+jobs:
+  linting:
+    name: Code-quality checks
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        toxenv:
+          - isort
+          - black
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-python@v2
+        with:
+          python-version: '3.9'
+      - name: Install dependencies
+        run: pip install tox
+      - run: tox
+        env:
+          TOXENV: ${{ matrix.toxenv }}
diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml
deleted file mode 100644
index 688f5fd..0000000
--- a/.github/workflows/python-publish.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-# This workflows will upload a Python Package using Twine when a release is created
-# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
-
-name: Upload Python Package
-
-on:
-  release:
-    types: [created]
-
-jobs:
-  deploy:
-
-    runs-on: ubuntu-latest
-
-    steps:
-    - uses: actions/checkout@v2
-    - name: Set up Python
-      uses: actions/setup-python@v2
-      with:
-        python-version: '3.x'
-    - name: Install dependencies
-      run: |
-        python -m pip install --upgrade pip
-        pip install setuptools wheel twine
-    - name: Build and publish
-      env:
-        TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
-        TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
-      run: |
-        python setup.py sdist bdist_wheel
-        twine upload dist/*
diff --git a/.gitignore b/.gitignore
index 3d083ac..90d760e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ Documentation/
 .pydevproject
 .idea/
 .coverage
+reports/
 .ve*
 cover/
 .vagrant/
diff --git a/MANIFEST.in b/MANIFEST.in
index a1c8306..efa59ad 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,22 +1,12 @@
-include .coveragerc
-include .editorconfig
-include .isort.cfg
+include *.rst
+include *.typed
 include LICENSE
-include README.rst
-include MANIFEST.in
-include Makefile
-include setup.cfg
-include setup.py
-include manage.py
-include tox.ini
-recursive-include requirements *.txt
-recursive-include tests *.py
 recursive-include django_admin_index *.html
+recursive-include django_admin_index *.txt
+recursive-include django_admin_index *.po
 recursive-include django_admin_index/static *.css
-recursive-exclude * __pycache__
-recursive-exclude * *.py[co]
-recursive-exclude * .*.sw*
-recursive-exclude django_admin_index *.map
-exclude *.json
-exclude *.rst
+recursive-include django_admin_index/static *.css.map
 recursive-include scss *.scss
+global-exclude __pycache__
+global-exclude *.py[co]
+global-exclude .*.sw*
diff --git a/README.rst b/README.rst
index 3310215..dfd0974 100644
--- a/README.rst
+++ b/README.rst
@@ -3,11 +3,11 @@ Admin Index for Django
 ======================
 
 :Version: 1.6.0
-:Download: https://pypi.python.org/pypi/django_admin_index
+:Download: https://pypi.python.org/pypi/django-admin-index
 :Source: https://github.com/maykinmedia/django-admin-index
 :Keywords: django, admin, dashboard
 
-|build-status| |coverage| |license| |pyversion| |djversion|
+|build-status| |code-quality| |black| |coverage| |license| |python-versions| |django-versions| |pypi-version|
 
 About
 =====
@@ -38,7 +38,7 @@ To install using ``pip``:
 
 .. code-block:: console
 
-    $ pip install -U django_admin_index
+    $ pip install -U django-admin-index
 
 Usage
 =====
@@ -47,14 +47,16 @@ To use this with your project you need to follow these steps:
 
 #. Add ``django_admin_index`` and ``ordered_model`` to ``INSTALLED_APPS`` in
    your Django project's ``settings.py``. Make sure that
-   ``django_admin_index`` comes before ``django.contrib.admin``::
+   ``django_admin_index`` comes before ``django.contrib.admin``:
 
-    INSTALLED_APPS = (
-        'django_admin_index',
-        'ordered_model',
-        ...,
-        'django.contrib.admin',
-    )
+   .. code-block:: python
+
+      INSTALLED_APPS = (
+          "django_admin_index",
+          "ordered_model",
+          ...,
+          "django.contrib.admin",
+      )
 
    Note that there is no dash in the module name, only underscores.
 
@@ -149,7 +151,7 @@ you don't want this, you can add some CSS lines, like::
 .. code-block:: css
 
     #header { position: initial; }
-    .dropdown-menu { position: initial; }
+    .djai-dropdown-menu { position: initial; }
 
 
 Breadcrumbs
@@ -166,11 +168,69 @@ admin templates you desire (``change_list.html``, ``change_form.html``, etc.)
     {% endblock %}
 
 
+Contributors
+============
+
+Contributors and maintainers can install the project locally with all test dependencies
+in a virtualenv:
+
+.. code-block:: bash
+
+    (env) $ pip install -e .[tests,pep8,coverage,release]
+
+Running the test suite
+----------------------
+
+To run the tests for a single environment (currently installed in your virtualenv), use
+``pytest``:
+
+.. code-block:: bash
+
+    (env) $ pytest
+
+To run the complete build matrix, use ``tox``:
+
+.. code-block:: bash
+
+    (env) $ tox
+
+Developing the frontend
+-----------------------
+
+To develop the stylesheets, you can use the included test project:
+
+.. code-block:: bash
+
+    (env) $ python manage.py runserver
+
+You also want to install the frontend tooling and run the SCSS compilation to CSS in
+watch mode:
+
+.. code-block:: bash
+
+    npm install  # one time to get the dependencies installed
+    npm run watch
+
+Once the result is satisfactory, you can make a production build of the stylesheets:
+
+.. code-block:: bash
+
+    npm run scss
+
+Then, commit the changes and make a pull request.
+
 
 .. |build-status| image:: https://github.com/maykinmedia/django-admin-index/actions/workflows/ci.yml/badge.svg
     :alt: Build status
     :target: https://github.com/maykinmedia/django-admin-index/actions/workflows/ci.yml
 
+.. |code-quality| image:: https://github.com/maykinmedia/django-admin-index/workflows/Code%20quality%20checks/badge.svg
+     :alt: Code quality checks
+     :target: https://github.com/maykinmedia/django-admin-index/actions?query=workflow%3A%22Code+quality+checks%22
+
+.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
+    :target: https://github.com/psf/black
+
 .. |coverage| image:: https://codecov.io/github/maykinmedia/django-admin-index/coverage.svg?branch=master
     :target: https://codecov.io/github/maykinmedia/django-admin-index?branch=master
 
@@ -178,14 +238,16 @@ admin templates you desire (``change_list.html``, ``change_form.html``, etc.)
     :alt: BSD License
     :target: https://opensource.org/licenses/BSD-3-Clause
 
-.. |pyversion| image:: https://img.shields.io/pypi/pyversions/django-admin-index.svg
+.. |python-versions| image:: https://img.shields.io/pypi/pyversions/django-admin-index.svg
     :alt: Supported Python versions
     :target: http://pypi.python.org/pypi/django-admin-index/
 
-.. |djversion| image:: https://img.shields.io/badge/django-2.2%2C%203.0%2C%203.2%2C%204.0-blue.svg
+.. |django-versions| image:: https://img.shields.io/badge/django-2.2%2C%203.0%2C%203.2%2C%204.0-blue.svg
     :alt: Supported Django versions
     :target: http://pypi.python.org/pypi/django-admin-index/
 
+.. |pypi-version| image:: https://img.shields.io/pypi/v/django-admin-index.svg
+    :target: https://pypi.org/project/django-admin-index/
 
 .. |screenshot-1| image:: https://github.com/maykinmedia/django-admin-index/raw/master/docs/_assets/dashboard_with_menu_thumb.png
     :alt: Ordered dashboard with dropdown menu.
diff --git a/django_admin_index/__init__.py b/django_admin_index/__init__.py
index a52b453..8520a4f 100644
--- a/django_admin_index/__init__.py
+++ b/django_admin_index/__init__.py
@@ -1,42 +1,7 @@
-# -*- coding: utf-8 -*-
-"""Admin index for Django."""
 # :copyright: (c) 2017, Maykin Media BV.
 #             All rights reserved.
 # :license:   BSD (3 Clause), see LICENSE for more details.
-
-from __future__ import absolute_import, unicode_literals
-
-import re
-from collections import namedtuple
-
 __version__ = "1.6.0"
 __author__ = "Joeri Bekker"
-__contact__ = "joeri@maykinmedia.nl"
-__homepage__ = "https://github.com/maykinmedia/django-admin-index"
-__docformat__ = "restructuredtext"
-
-# -eof meta-
-
-version_info_t = namedtuple(
-    "version_info_t",
-    (
-        "major",
-        "minor",
-        "patch",
-        "releaselevel",
-        "serial",
-    ),
-)
-
-# bumpversion can only search for {current_version}
-# so we have to parse the version here.
-_temp = re.match(r"(\d+)\.(\d+).(\d+)(.+)?", __version__).groups()
-VERSION = version_info = version_info_t(
-    int(_temp[0]), int(_temp[1]), int(_temp[2]), _temp[3] or "", ""
-)
-del _temp
-del re
-
-__all__ = []
 
 default_app_config = "django_admin_index.apps.AdminIndexConfig"
diff --git a/django_admin_index/admin.py b/django_admin_index/admin.py
index 96bba58..fbe0efa 100644
--- a/django_admin_index/admin.py
+++ b/django_admin_index/admin.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 from django.contrib import admin
 
 from ordered_model.admin import OrderedModelAdmin
diff --git a/django_admin_index/apps.py b/django_admin_index/apps.py
index 944739c..f436af3 100644
--- a/django_admin_index/apps.py
+++ b/django_admin_index/apps.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 from django.apps import AppConfig, apps
 from django.core.checks import Error, Tags, Warning, register
 from django.utils.translation import gettext_lazy as _
diff --git a/django_admin_index/conf.py b/django_admin_index/conf.py
index 09ecc1e..e9e2433 100644
--- a/django_admin_index/conf.py
+++ b/django_admin_index/conf.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 from django.conf import settings as django_settings
 
 
diff --git a/django_admin_index/migrations/0001_initial.py b/django_admin_index/migrations/0001_initial.py
index 1c67a0a..c1b4e37 100644
--- a/django_admin_index/migrations/0001_initial.py
+++ b/django_admin_index/migrations/0001_initial.py
@@ -1,7 +1,5 @@
 # -*- coding: utf-8 -*-
 # Generated by Django 1.11.2 on 2017-07-02 08:40
-from __future__ import absolute_import, unicode_literals
-
 import django.contrib.contenttypes.models
 import django.db.models.deletion
 from django.db import migrations, models
diff --git a/django_admin_index/migrations/0002_auto_20170802_1754.py b/django_admin_index/migrations/0002_auto_20170802_1754.py
index 8fe3b8d..d9bf709 100644
--- a/django_admin_index/migrations/0002_auto_20170802_1754.py
+++ b/django_admin_index/migrations/0002_auto_20170802_1754.py
@@ -1,6 +1,4 @@
 # -*- coding: utf-8 -*-
-from __future__ import absolute_import, unicode_literals
-
 from django.db import migrations, models
 
 
diff --git a/django_admin_index/models.py b/django_admin_index/models.py
index d2d8f6d..bbdc32c 100644
--- a/django_admin_index/models.py
+++ b/django_admin_index/models.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, unicode_literals
-
 from django.contrib.admin import site
 from django.contrib.contenttypes.models import ContentType
 from django.db import models
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..a9e8cdd
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools >= 30.3.0"]  # version supporting setup.cfg
+build-backend = "setuptools.build_meta"
diff --git a/requirements/default.txt b/requirements/default.txt
deleted file mode 100644
index 5647c50..0000000
--- a/requirements/default.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-django-ordered-model>=3.5
diff --git a/requirements/pkgutils.txt b/requirements/pkgutils.txt
deleted file mode 100644
index 7e0693f..0000000
--- a/requirements/pkgutils.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-isort>=5.1.4
-black>=19.10b0
-tox>=2.3.1
-bumpversion>=0.6.0
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
index 1ee316c..bfe4ed1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,25 +1,78 @@
-[tool:pytest]
-testpaths = tests/unit
-python_classes = test_*
-DJANGO_SETTINGS_MODULE=tests.proj.settings
+# setuptools config
+# see http://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files
+[metadata]
+name = django-admin-index
+version = 1.6.0
+description = Admin index for Django
+long_description = file: README.rst
+url = https://github.com/maykinmedia/django-admin-index
+project_urls =
+    ; Documentation = http://django-admin-index.readthedocs.io/en/latest/
+    Changelog = https://github.com/maykinmedia/django-admin-index/blob/master/CHANGELOG.rst
+    Bug Tracker = https://github.com/maykinmedia/django-admin-index/issues
+    Source Code = https://github.com/maykinmedia/django-admin-index
+license = BSD
+author = Joeri Bekker
+author_email = joeri@maykinmedia.nl
+keywords = django, index, dashboard
+classifiers =
+    Development Status :: 5 - Production/Stable
+    Framework :: Django
+    Framework :: Django :: 2.2
+    Framework :: Django :: 3.2
+    Framework :: Django :: 4.0
+    Intended Audience :: Developers
+    License :: OSI Approved :: BSD License
+    Operating System :: OS Independent
+    Programming Language :: Python
+    Programming Language :: Python :: 3
+    Programming Language :: Python :: 3 :: Only
+    Programming Language :: Python :: 3.7
+    Programming Language :: Python :: 3.8
+    Programming Language :: Python :: 3.9
+    Programming Language :: Python :: 3.10
+    Programming Language :: Python :: Implementation :: CPython
+    Programming Language :: Python :: Implementation :: PyPy
+    Topic :: Software Development :: Libraries :: Python Modules
 
-[flake8]
-# classes can be lowercase, arguments and variables can be uppercase
-# whenever it makes the code more readable.
-ignore = N806, N802, N801, N803
-max-line-length = 119
-exclude = **/migrations/*
+[options]
+zip_safe = False
+include_package_data = True
+packages = find:
+install_requires =
+    django >= 2.2
+    django-ordered-model >= 3.5
+tests_require =
+    pytest
+    pytest-django
+    tox
+    isort
+    black
 
-[pep257]
-ignore = D102,D104,D203,D105,D213
-match-dir = [^migrations]
+[options.packages.find]
+include =
+    django_admin_index
+    django_admin_index.*
 
-[wheel]
-universal = 1
+[options.extras_require]
+tests =
+    pytest
+    pytest-django
+    tox
+    isort
+    black
+pep8 = flake8
+coverage = pytest-cov
+; docs =
+;     sphinx
+;     sphinx-rtd-theme
+release =
+    bump2version
+    twine
 
-[check-manifest]
-ignore =
-    docs/_build*
+# 3rd party configuration
+[aliases]
+test=pytest
 
 [isort]
 combine_as_imports = true
@@ -34,3 +87,17 @@ skip = env,.tox,.history,.eggs
 known_django=django
 known_first_party=django_admin_index
 sections=FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
+
+[tool:pytest]
+testpaths = tests/unit
+python_classes = test_*
+DJANGO_SETTINGS_MODULE=tests.proj.settings
+
+[pep8]
+[flake8]
+max-line-length = 88
+exclude =
+    env
+    .tox
+    doc
+    **/migrations/*
diff --git a/setup.py b/setup.py
index 7266ca2..6068493 100644
--- a/setup.py
+++ b/setup.py
@@ -1,152 +1,3 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
+from setuptools import setup
 
-import codecs
-import os
-import re
-import sys
-
-import setuptools
-import setuptools.command.test
-
-try:
-    import platform
-    _pyimp = platform.python_implementation
-except (AttributeError, ImportError):
-    def _pyimp():
-        return 'Python'
-
-NAME = 'django_admin_index'
-
-E_UNSUPPORTED_PYTHON = '%s requires %%s %%s or later!' % (NAME,)
-
-PYIMP = _pyimp()
-PY26_OR_LESS = sys.version_info < (2, 7)
-PY3 = sys.version_info[0] == 3
-PY33_OR_LESS = PY3 and sys.version_info < (3, 4)
-PYPY_VERSION = getattr(sys, 'pypy_version_info', None)
-PYPY = PYPY_VERSION is not None
-PYPY24_ATLEAST = PYPY_VERSION and PYPY_VERSION >= (2, 4)
-
-if PY26_OR_LESS:
-    raise Exception(E_UNSUPPORTED_PYTHON % (PYIMP, '2.7'))
-elif PY33_OR_LESS and not PYPY24_ATLEAST:
-    raise Exception(E_UNSUPPORTED_PYTHON % (PYIMP, '3.4'))
-
-# -*- Classifiers -*-
-
-classes = """
-    Development Status :: 5 - Production/Stable
-    License :: OSI Approved :: BSD License
-    Programming Language :: Python
-    Programming Language :: Python :: 3
-    Programming Language :: Python :: 3.7
-    Programming Language :: Python :: 3.8
-    Programming Language :: Python :: 3.10
-    Programming Language :: Python :: Implementation :: CPython
-    Programming Language :: Python :: Implementation :: PyPy
-    Framework :: Django
-    Framework :: Django :: 2.2
-    Framework :: Django :: 3.0
-    Framework :: Django :: 3.2
-    Framework :: Django :: 4.0
-    Operating System :: OS Independent
-    Topic :: Communications
-    Topic :: System :: Distributed Computing
-    Topic :: Software Development :: Libraries :: Python Modules
-"""
-classifiers = [s.strip() for s in classes.split('\n') if s]
-
-# -*- Distribution Meta -*-
-
-re_meta = re.compile(r'__(\w+?)__\s*=\s*(.*)')
-re_doc = re.compile(r'^"""(.+?)"""')
-
-
-def add_default(m):
-    attr_name, attr_value = m.groups()
-    return ((attr_name, attr_value.strip("\"'")),)
-
-
-def add_doc(m):
-    return (('doc', m.groups()[0]),)
-
-pats = {re_meta: add_default,
-        re_doc: add_doc}
-here = os.path.abspath(os.path.dirname(__file__))
-with open(os.path.join(here, NAME, '__init__.py')) as meta_fh:
-    meta = {}
-    for line in meta_fh:
-        if line.strip() == '# -eof meta-':
-            break
-        for pattern, handler in pats.items():
-            m = pattern.match(line.strip())
-            if m:
-                meta.update(handler(m))
-
-# -*- Installation Requires -*-
-
-
-def strip_comments(l):
-    return l.split('#', 1)[0].strip()
-
-
-def _pip_requirement(req):
-    if req.startswith('-r '):
-        _, path = req.split()
-        return reqs(*path.split('/'))
-    return [req]
-
-
-def _reqs(*f):
-    return [
-        _pip_requirement(r) for r in (
-            strip_comments(l) for l in open(
-                os.path.join(os.getcwd(), 'requirements', *f)).readlines()
-        ) if r]
-
-
-def reqs(*f):
-    return [req for subreq in _reqs(*f) for req in subreq]
-
-# -*- Long Description -*-
-
-if os.path.exists('README.rst'):
-    long_description = codecs.open('README.rst', 'r', 'utf-8').read()
-else:
-    long_description = 'See http://pypi.python.org/pypi/%s' % (NAME,)
-
-# -*- %%% -*-
-
-
-class pytest(setuptools.command.test.test):
-    user_options = [('pytest-args=', 'a', 'Arguments to pass to py.test')]
-
-    def initialize_options(self):
-        setuptools.command.test.test.initialize_options(self)
-        self.pytest_args = []
-
-    def run_tests(self):
-        import pytest
-        sys.exit(pytest.main(self.pytest_args))
-
-
-setuptools.setup(
-    name=NAME,
-    packages=setuptools.find_packages(exclude=['tests', 'tests.*']),
-    version=meta['version'],
-    description=meta['doc'],
-    long_description=long_description,
-    keywords='django index dashboard',
-    author=meta['author'],
-    author_email=meta['contact'],
-    url=meta['homepage'],
-    platforms=['any'],
-    license='BSD',
-    classifiers=classifiers,
-    install_requires=reqs('default.txt'),
-    tests_require=reqs('test.txt'),
-    cmdclass={'test': pytest},
-    zip_safe=False,
-    include_package_data=True,
-)
+setup()
diff --git a/tox.ini b/tox.ini
index a3e80e1..f2d4856 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,30 +1,45 @@
 [tox]
 envlist =
-  py{37,38,39}-django22,
-  py{38,39,310}-django{32,40},
-  isort,
+  py{37,38,39}-django22
+  py{38,39,310}-django{32,40}
+  isort
   black
 skip_missing_interpreters = true
 
+[gh-actions:env]
+DJANGO =
+    2.2: django22
+    3.2: django32
+    4.0: django40
+
 [testenv]
+setenv =
+  DJANGO_SETTINGS_MODULE=tests.proj.settings
+  PYTHONPATH={toxinidir}
+extras =
+    tests
+    coverage
 deps =
-  -r{toxinidir}/requirements/default.txt
-  -r{toxinidir}/requirements/test.txt
-  -r{toxinidir}/requirements/test-ci.txt
+  django22: Django~=2.2.0
+  django32: Django~=3.2.0
+  django40: Django~=4.0.0
 
-  django22: Django~=2.2
-  django32: Django~=3.2
-  django40: Django~=4.0
 commands =
-    py.test -xv --cov=django_admin_index --cov-report=term --cov-report=xml --no-cov-on-fail []
+    py.test -xv \
+        --junitxml=reports/junit.xml \
+        --cov=django_admin_index \
+        --cov-report=term \
+        --cov-report xml:reports/coverage-{envname}.xml \
+        --no-cov-on-fail \
+        {posargs}
 
 [testenv:isort]
-deps = isort
+extras = tests
 skipsdist = True
-commands = isort --recursive --check-only --diff {toxinidir}/django_admin_index {toxinidir}/tests
+commands = isort --check-only --diff .
 
 [testenv:black]
-deps = black
+extras = tests
 skipsdist = True
-commands = black {toxinidir}/django_admin_index {toxinidir}/tests
+commands = black --check django_admin_index tests setup.py
 


Test patch:
diff --git a/requirements/test-ci.txt b/requirements/test-ci.txt
deleted file mode 100644
index 42f6df9..0000000
--- a/requirements/test-ci.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-pytest-cov==2.8.1
-codecov==2.0.15
diff --git a/requirements/test.txt b/requirements/test.txt
deleted file mode 100644
index 47c9293..0000000
--- a/requirements/test.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-case>=1.3.1
-pytest==6.2.5
-pytest-django==4.5.2
-pytz>dev