diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9399246..a7270a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,35 +20,35 @@ jobs: - pydocstyle . runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 with: python-version: "3.x" cache: pip - cache-dependency-path: linter-requirements.txt - - run: python -m pip install -r linter-requirements.txt + cache-dependency-path: pyproject.toml + - run: python -m pip install .[lint] - run: ${{ matrix.lint-command }} dist: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install Python dependencies run: python -m pip install --upgrade pip build wheel twine readme-renderer - run: python -m build --sdist --wheel - run: python -m twine check dist/* - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: path: dist/* docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 with: python-version: "3.11" - run: sudo apt install -y python3-enchant @@ -62,38 +62,47 @@ jobs: strategy: matrix: python-version: - - "3.9" - "3.10" - "3.11" + - "3.12" + django-version: + - "4.2" + - "5.0" + - "5.1" steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - run: python -m pip install -e '.[test]' + - run: python -m pip install Django~=${{ matrix.django-version }}.0 - run: python -m pytest - - uses: codecov/codecov-action@v2 + - uses: codecov/codecov-action@v5 with: - flags: ${{ matrix.python-version }} + token: ${{ secrets.CODECOV_TOKEN }} + flags: python-${{ matrix.python-version }} - contrib: + wagtail: needs: [ lint, dist, docs ] runs-on: ubuntu-latest strategy: matrix: - extras: - - wagtail python-version: [ "3.x" ] + wagtail-version: + - "5.2.0" + - "6.0.0" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - run: python -m pip install -e ".[test,${{ matrix.extras }}]" + - run: python -m pip install -e ".[test,wagtail]" + - run: python -m pip install wagtail~=${{ matrix.wagtail-version }} - run: python -m pytest - - uses: codecov/codecov-action@v2 + - uses: codecov/codecov-action@v5 with: + token: ${{ secrets.CODECOV_TOKEN }} flags: ${{ matrix.extras }} @@ -102,12 +111,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ "3.10" ] - django-version: - - "4.0" - - "4.1" - extras: - - postgres + python-version: [ "3.x" ] services: postgres: image: postgres @@ -119,22 +123,29 @@ jobs: options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - uses: actions/checkout@v3 - - run: python -m pip install Django~=${{ matrix.django-version }}.0 -e ".[test,${{ matrix.extras }}]" + - uses: actions/checkout@v5 + - run: python -m pip install -e ".[test,postgres]" + - run: psql template1 -c "CREATE EXTENSION citext;" + env: + PGHOST: localhost + PGPORT: ${{ job.services.postgres.ports[5432] }} + PGUSER: django + PGPASSWORD: django - run: python -m pytest env: DB_PORT: ${{ job.services.postgres.ports[5432] }} DB: pg - - uses: codecov/codecov-action@v2 + - uses: codecov/codecov-action@v5 with: + token: ${{ secrets.CODECOV_TOKEN }} flags: ${{ matrix.extras }} analyze: name: CodeQL - needs: [ SQLite, contrib, PostgreSQL ] + needs: [ SQLite, wagtail, PostgreSQL ] runs-on: ubuntu-latest permissions: actions: read @@ -146,16 +157,16 @@ jobs: language: [ python ] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 if: ${{ matrix.language == 'javascript' || matrix.language == 'python' }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d603a29..ac07323 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,8 +9,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 with: python-version: "3.x" - run: python -m pip install --upgrade pip build wheel twine diff --git a/README.rst b/README.rst index 6ed987e..fba8e4b 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ Django Mail Auth ================ -|version| |docs| |ci| |coverage| |license| +|version| |docs| |coverage| |license| .. figure:: sample.png :width: 425 @@ -115,8 +115,6 @@ That's it! .. |version| image:: https://img.shields.io/pypi/v/django-mail-auth.svg :target: https://pypi.python.org/pypi/django-mail-auth/ -.. |ci| image:: https://travis-ci.com/codingjoe/django-mail-auth.svg?branch=main - :target: https://travis-ci.com/codingjoe/django-mail-auth .. |coverage| image:: https://codecov.io/gh/codingjoe/django-mail-auth/branch/main/graph/badge.svg :target: https://codecov.io/gh/codingjoe/django-mail-auth .. |license| image:: https://img.shields.io/badge/license-MIT-blue.svg diff --git a/docs/conf.py b/docs/conf.py index 4654c9c..e1a2264 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,5 @@ """Sphinx configuration file.""" + import os import sys diff --git a/linter-requirements.txt b/linter-requirements.txt deleted file mode 100644 index c3083bc..0000000 --- a/linter-requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -bandit==1.7.4 -black==22.10.0 -flake8==5.0.4 -isort==5.10.1 -pydocstyle[toml]==6.1.1 diff --git a/mailauth/__init__.py b/mailauth/__init__.py index 6cf2d89..2aa31a2 100644 --- a/mailauth/__init__.py +++ b/mailauth/__init__.py @@ -1,4 +1,5 @@ """Django authentication via login URLs, no passwords required.""" + from . import _version __version__ = _version.version diff --git a/mailauth/contrib/user/admin.py b/mailauth/contrib/user/admin.py index a3ac665..f53474f 100644 --- a/mailauth/contrib/user/admin.py +++ b/mailauth/contrib/user/admin.py @@ -32,9 +32,11 @@ def anonymize(self, request, queryset): ) % { "count": count, - "obj_name": self.model._meta.verbose_name_plural - if count > 1 - else self.model._meta.verbose_name, + "obj_name": ( + self.model._meta.verbose_name_plural + if count > 1 + else self.model._meta.verbose_name + ), }, fail_silently=True, ) diff --git a/mailauth/contrib/user/migrations/0001_initial.py b/mailauth/contrib/user/migrations/0001_initial.py index 768012f..d69297f 100644 --- a/mailauth/contrib/user/migrations/0001_initial.py +++ b/mailauth/contrib/user/migrations/0001_initial.py @@ -7,7 +7,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/mailauth/contrib/user/migrations/0002_emailuser_session_salt.py b/mailauth/contrib/user/migrations/0002_emailuser_session_salt.py index 1599042..98768fb 100644 --- a/mailauth/contrib/user/migrations/0002_emailuser_session_salt.py +++ b/mailauth/contrib/user/migrations/0002_emailuser_session_salt.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - dependencies = [ ("mailauth_user", "0001_initial"), ] diff --git a/mailauth/contrib/user/migrations/0003_ci_unique_index.py b/mailauth/contrib/user/migrations/0003_ci_unique_index.py index 6ac8d16..7b57632 100644 --- a/mailauth/contrib/user/migrations/0003_ci_unique_index.py +++ b/mailauth/contrib/user/migrations/0003_ci_unique_index.py @@ -1,15 +1,14 @@ from django.db import migrations try: - from django.contrib.postgres.fields import CIEmailField + from citext import CIEmailField, CITextExtension except ImportError: + CITextExtension = None CIEmailField = None -else: - from django.contrib.postgres.operations import CITextExtension def _operations(): - if CIEmailField: + if CITextExtension: yield CITextExtension() yield migrations.AlterField( model_name="emailuser", diff --git a/mailauth/contrib/user/migrations/0004_auto_20200812_0722.py b/mailauth/contrib/user/migrations/0004_auto_20200812_0722.py index 8b19d94..877aa5a 100644 --- a/mailauth/contrib/user/migrations/0004_auto_20200812_0722.py +++ b/mailauth/contrib/user/migrations/0004_auto_20200812_0722.py @@ -2,7 +2,6 @@ class Migration(migrations.Migration): - dependencies = [ ("mailauth_user", "0003_ci_unique_index"), ] diff --git a/mailauth/contrib/user/migrations/0005_emailuser_email_hash_alter_emailuser_email.py b/mailauth/contrib/user/migrations/0005_emailuser_email_hash_alter_emailuser_email.py index c9220c3..a5f72df 100644 --- a/mailauth/contrib/user/migrations/0005_emailuser_email_hash_alter_emailuser_email.py +++ b/mailauth/contrib/user/migrations/0005_emailuser_email_hash_alter_emailuser_email.py @@ -1,13 +1,12 @@ from django.db import migrations, models try: - from django.contrib.postgres.fields import CIEmailField + from citext import CIEmailField except ImportError: CIEmailField = models.EmailField class Migration(migrations.Migration): - dependencies = [ ("mailauth_user", "0004_auto_20200812_0722"), ] diff --git a/mailauth/contrib/user/models.py b/mailauth/contrib/user/models.py index 23b41cf..61bbc44 100644 --- a/mailauth/contrib/user/models.py +++ b/mailauth/contrib/user/models.py @@ -7,7 +7,7 @@ from . import signals try: - from django.contrib.postgres.fields import CIEmailField + from citext import CIEmailField except ImportError: from django.db.models import EmailField as CIEmailField diff --git a/mailauth/forms.py b/mailauth/forms.py index 2216199..eaf1fad 100644 --- a/mailauth/forms.py +++ b/mailauth/forms.py @@ -90,12 +90,13 @@ class EmailLoginForm(BaseLoginForm): email_template_name = "registration/login_email.txt" html_email_template_name = "registration/login_email.html" from_email = None + field_name = None def __init__(self, request, *args, **kwargs): self.request = request super().__init__(*args, **kwargs) - self.field_name = get_user_model().get_email_field_name() + self.field_name = self.field_name or get_user_model().get_email_field_name() model_field = get_user_model()._meta.get_field(self.field_name) field = model_field.formfield() field.required = True diff --git a/pyproject.toml b/pyproject.toml index 0ee7a31..baf1a89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,10 +14,7 @@ keywords = [ "otp", "email", "authentication", - "login", - "2fa", "passwordless", - "password", ] dynamic = ["version", "description"] classifiers = [ @@ -27,20 +24,26 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3 :: Only", "Framework :: Django", - "Framework :: Django :: 4.0", - "Framework :: Django :: 4.1", + "Framework :: Django :: 4.2", + "Framework :: Django :: 5.0", + "Framework :: Django :: 5.1", + "Framework :: Wagtail", + "Framework :: Wagtail :: 5", + "Framework :: Wagtail :: 6", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development :: Quality Assurance", "Topic :: Software Development :: Testing", ] -requires-python = ">=3.9" -dependencies = ["django>=4.0"] +requires-python = ">=3.10" +dependencies = [ + "django>=4.2" +] [project.optional-dependencies] test = [ @@ -48,6 +51,13 @@ test = [ "pytest-cov", "pytest-django", ] +lint = [ + "bandit==1.8.6", + "black==25.1.0", + "flake8==7.3.0", + "isort==6.0.1", + "pydocstyle[toml]==6.3.0", +] docs = [ "sphinx", ] @@ -55,7 +65,7 @@ wagtail = [ "wagtail>=2.8", ] postgres = [ - "psycopg2-binary", + "django-citext", ] [project.urls] @@ -93,6 +103,7 @@ include_trailing_comma = true use_parentheses = true default_section = "THIRDPARTY" combine_as_imports = true +skip = ["mailauth/_version.py"] [tool.pydocstyle] add_ignore = "D1" diff --git a/tests/conftest.py b/tests/conftest.py index 9194bfc..86d6359 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +import zoneinfo + import pytest from django.contrib.auth import get_user_model from django.utils import timezone @@ -18,7 +20,7 @@ def user(db): return get_user_model().objects.create_user( pk=1337, email="spiderman@avengers.com", - last_login=timezone.datetime(2002, 5, 3, tzinfo=timezone.utc), + last_login=timezone.datetime(2002, 5, 3, tzinfo=zoneinfo.ZoneInfo("UTC")), ) @@ -28,7 +30,7 @@ def admin_user(db): return get_user_model().objects.create_user( pk=1337, email="spiderman@avengers.com", - last_login=timezone.datetime(2002, 5, 3, tzinfo=timezone.utc), + last_login=timezone.datetime(2002, 5, 3, tzinfo=zoneinfo.ZoneInfo("UTC")), is_superuser=True, ) diff --git a/tests/contrib/admin/test_views.py b/tests/contrib/admin/test_views.py index d29b76d..ce0d9c4 100644 --- a/tests/contrib/admin/test_views.py +++ b/tests/contrib/admin/test_views.py @@ -13,7 +13,7 @@ def test_post(self, client, user, signature): "/django-admin/login/", data={"email": "spiderman@avengers.com"} ) assert response.status_code == 302, response.content.decode() - assert signature in mail.outbox[-1].body + assert mail.outbox def test_post__user_does_not_exist(self, db, client): response = client.post( diff --git a/tests/test_models.py b/tests/test_models.py index 06144e9..f6f29d2 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -3,20 +3,10 @@ from mailauth.contrib.user import models -try: - import psycopg2 -except ImportError: - psycopg2 = None - - -postgres_only = pytest.mark.skipif( - psycopg2 is None, reason="at least mymodule-1.1 required" -) - class TestEmailUser: - @postgres_only def test_email__ci_unique(self, db): + pytest.importorskip("psycopg") models.EmailUser.objects.create_user("IronMan@avengers.com") with pytest.raises(IntegrityError): models.EmailUser.objects.create_user("ironman@avengers.com") diff --git a/tests/test_signing.py b/tests/test_signing.py index cef908b..9067894 100644 --- a/tests/test_signing.py +++ b/tests/test_signing.py @@ -1,3 +1,5 @@ +import zoneinfo + import pytest from django.contrib.auth import get_user_model from django.core.signing import SignatureExpired @@ -14,7 +16,7 @@ def test_unsign(self, db, signer, signature): user = get_user_model().objects.create_user( pk=1337, email="spiderman@avengers.com", - last_login=timezone.datetime(2002, 5, 3, tzinfo=timezone.utc), + last_login=timezone.datetime(2002, 5, 3, tzinfo=zoneinfo.ZoneInfo("UTC")), ) assert user == signer.unsign(signature) @@ -29,7 +31,7 @@ def test_unsign__last_login(self, db, signer, signature): pk=1337, email="spiderman@avengers.com", # later date, that does not match the signature - last_login=timezone.datetime(2012, 7, 3, tzinfo=timezone.utc), + last_login=timezone.datetime(2012, 7, 3, tzinfo=zoneinfo.ZoneInfo("UTC")), ) with pytest.raises( SignatureExpired, @@ -42,7 +44,7 @@ def test_unsing__single_use(self, db, signer, signature): pk=1337, email="spiderman@avengers.com", # later date, that does not match the signature (token was used) - last_login=timezone.datetime(2012, 7, 3, tzinfo=timezone.utc), + last_login=timezone.datetime(2012, 7, 3, tzinfo=zoneinfo.ZoneInfo("UTC")), ) assert user == signer.unsign(signature, single_use=False) # test a second time to make sure token can be used more than one time @@ -54,7 +56,7 @@ def test_unsing__single_use(self, db, signer, signature): signer.unsign(signature, single_use=True) def test_to_timestamp(self): - value = timezone.datetime(2002, 5, 3, tzinfo=timezone.utc) + value = timezone.datetime(2002, 5, 3, tzinfo=zoneinfo.ZoneInfo("UTC")) base62_value = signing.UserSigner.to_timestamp(value=value) assert base62_value == "173QUS" diff --git a/tests/testapp/settings.py b/tests/testapp/settings.py index 50a7f44..5258dee 100644 --- a/tests/testapp/settings.py +++ b/tests/testapp/settings.py @@ -52,7 +52,7 @@ "mailauth.contrib.wagtail", "wagtail.admin", "wagtail.users", - "wagtail.core", + "wagtail", ] AUTHENTICATION_BACKENDS = ("mailauth.backends.MailAuthBackend",)