diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml new file mode 100644 index 0000000..52c09c9 --- /dev/null +++ b/.github/workflows/packaging.yml @@ -0,0 +1,141 @@ +name: Packaging + +on: + - push + +jobs: + format: + name: Check formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install tox + run: python -m pip install tox + + - name: Run black + run: tox -e format + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install tox + run: python -m pip install tox + + - name: Run flake8 + run: tox -e lint + + typecheck: + name: Type check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install tox + run: python -m pip install tox + + - name: Run mypy + run: tox -e typecheck + + test: + name: Test + runs-on: ubuntu-latest + strategy: + matrix: + python: + - version: "3.13" + toxenv: "py313" + - version: "3.12" + toxenv: "py312" + - version: "3.11" + toxenv: "py311" + - version: "3.10" + toxenv: "py310" + - version: "3.9" + toxenv: "py39" + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python.version }} + + - name: Install tox + run: python -m pip install tox + + - name: Run pytest + run: tox -e ${{ matrix.python.toxenv }} + + build_source_dist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install build + run: python -m pip install build + + - name: Run build + run: python -m build --sdist + + - uses: actions/upload-artifact@v4 + with: + path: dist/*.tar.gz + + publish: + needs: [format, lint, typecheck, test] + if: startsWith(github.ref, 'refs/tags') + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write + contents: write + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.9 + + - name: Install pypa/build + run: python -m pip install build + + - name: Build distribution + run: python -m build --outdir dist/ + + - name: Publish distribution to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository_url: https://test.pypi.org/legacy/ + + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + - name: Publish distribution to GitHub release + uses: softprops/action-gh-release@v2 + with: + files: | + dist/django_webmention-*.whl + dist/django_webmention-*.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/pyproject.toml b/pyproject.toml index 7276f76..debf12f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,235 @@ +[project] +name = "django-webmention" +version = "3.0.0" +description = "A pluggable implementation of webmention for Django projects" +authors = [ + { name = "Dane Hillard", email = "github@danehillard.com" }, +] +license = { file = "LICENSE" } +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Framework :: Django", + "Framework :: Django :: 3.2", + "Framework :: Django :: 4.0", + "Framework :: Django :: 4.1", + "Topic :: Internet :: WWW/HTTP :: Indexing/Search", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "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.13", +] +dependencies = [ + "Django>=4.2.0", + "requests>=2.32.3", +] + +[project.urls] +Repository = "https://github.com/easy-as-python/django-webmention" + +[tool.setuptools.packages.find] +where = ["src"] +exclude = ["test*"] + +###################### +# Tool configuration # +###################### + [tool.black] line-length = 120 -target-version = ['py35', 'py36', 'py37', 'py38'] +target-version = ["py39", "py310", "py311", "py312", "py313"] + +[tool.mypy] +python_version = "3.9" +warn_unused_configs = true +show_error_context = true +pretty = true +namespace_packages = true +check_untyped_defs = true + +[[tool.mypy.overrides]] +module = [ + "django.core.urlresolvers", +] +ignore_missing_imports = true + +[tool.coverage.run] +branch = true +omit = [ + "manage.py", + "webmention/checks.py", + "*test*", + "*/migrations/*", + "*/admin.py", + "*/__init__.py", +] + +[tool.coverage.report] +precision = 2 +show_missing = true +skip_covered = true + +[tool.coverage.paths] +source = [ + "src/webmention", + "*/site-packages/webmention", +] + +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "tests.settings" +testpaths = ["tests"] +addopts = ["-ra", "-q", "--cov=webmention"] +xfail_strict = true + +[tool.tox] +envlist = [ + "py39-django4.2", + "py39-django5.0", + "py39-django5.1", + "py310-django4.2", + "py310-django5.0", + "py310-django5.1", + "py311-django4.2", + "py311-django5.0", + "py311-django5.1", + "py312-django4.2", + "py312-django5.0", + "py312-django5.1", + "py313-django4.2", + "py313-django5.0", + "py313-django5.1", +] + +[tool.tox.env_run_base] +deps = [ + "pytest", + "pytest-cov", + "pytest-django", +] +commands = [ + ["pytest", { replace = "posargs", default = [], extend = true }], +] + +[tool.tox.env."py39-django4.2"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=4.2,<4.3", +] + +[tool.tox.env."py39-django5.0"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=5.0,<5.1", +] + +[tool.tox.env."py39-django5.1"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=5.1,<5.2", +] + +[tool.tox.env."py310-django4.2"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=4.2,<4.3", +] + +[tool.tox.env."py310-django5.0"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=5.0,<5.1", +] + +[tool.tox.env."py310-django5.1"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=5.1,<5.2", +] + +[tool.tox.env."py311-django4.2"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=4.2,<4.3", +] + +[tool.tox.env."py311-django5.0"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=5.0,<5.1", +] + +[tool.tox.env."py311-django5.1"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=5.1,<5.2", +] + +[tool.tox.env."py312-django4.2"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=4.2,<4.3", +] + +[tool.tox.env."py312-django5.0"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=5.0,<5.1", +] + +[tool.tox.env."py312-django5.1"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=5.1,<5.2", +] + +[tool.tox.env."py313-django4.2"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=4.2,<4.3", +] + +[tool.tox.env."py313-django5.0"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=5.0,<5.1", +] + +[tool.tox.env."py313-django5.1"] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "Django>=5.1,<5.2", +] + +[tool.tox.env.lint] +skip_install = true +deps = [ + "ruff", +] +commands = [ + ["ruff", "check", { replace = "posargs", default = ["--diff", "src/webmention", "tests"], extend = true }], +] + +[tool.tox.env.format] +skip_install = true +deps = [ + "black", +] +commands = [ + ["black", { replace = "posargs", default = ["--check", "--diff", "src/webmention", "tests"], extend = true }], +] + +[tool.tox.env.typecheck] +deps = [ + { replace = "ref", of = ["tool", "tox", "env_run_base", "deps"], extend = true }, + "mypy", + "django-types", + "types-requests", +] +commands = [ + ["mypy", { replace = "posargs", default = ["src/webmention", "tests"], extend = true }], +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 8ffb45e..0000000 --- a/setup.cfg +++ /dev/null @@ -1,89 +0,0 @@ -[metadata] -name = django-webmention -version = 3.0.0 -description = A pluggable implementation of webmention for Django projects -author = Dane Hillard -author_email = github@danehillard.com -long_description = file: README.md -long_description_content_type = text/markdown -url = https://github.com/easy-as-python/django-webmention -license = MIT -license_file = LICENSE -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - Framework :: Django - Framework :: Django :: 3.2 - Framework :: Django :: 4.0 - Framework :: Django :: 4.1 - Topic :: Internet :: WWW/HTTP :: Indexing/Search - License :: OSI Approved :: MIT License - Programming Language :: Python - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - -[options] -package_dir = =src -packages = find: -install_requires = - Django>=2.2.0 - requests>=2.7.0 - -[options.packages.find] -where = src - -[options.extras_require] -test = - coverage - pytest - pytest-cov - pytest-django -lint = - pyflakes - black - -[coverage:run] -branch = True -omit = - manage.py - setup.py - webmention/checks.py - *test* - */migrations/* - */admin.py - */__init__.py - -[coverage:report] -precision = 2 -show_missing = True -skip_covered = True - -[tool:pytest] -DJANGO_SETTINGS_MODULE = tests.test_settings -python_files = - tests.py - test_*.py -addopts = -ra -q --cov=webmention - -[tox:tox] -envlist = {py37,py38,py39,py310,py311}-django{3.2,4.0,4.1} - -[testenv] -extras = test -commands = - pytest {posargs} -deps = - django3.1: Django>=3.2,<3.3 - django4.0: Django>=4.0,<4.1 - django4.1: Django>=4.1,<4.2 - -[testenv:lint] -extras = lint -commands = - pyflakes src/webmention tests - black --check src/webmention tests diff --git a/setup.py b/setup.py deleted file mode 100644 index 056ba45..0000000 --- a/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -import setuptools - - -setuptools.setup() diff --git a/src/webmention/models.py b/src/webmention/models.py index 55675e2..a0fe851 100644 --- a/src/webmention/models.py +++ b/src/webmention/models.py @@ -1,8 +1,10 @@ +from django.contrib.admin import display from django.db import models from django.utils.html import format_html class WebMentionResponse(models.Model): + id: int response_body = models.TextField() response_to = models.URLField() source = models.URLField() @@ -18,15 +20,13 @@ class Meta: def __str__(self): return self.source + @display(description="source") def source_for_admin(self): - return format_html('{href}'.format(href=self.source)) - - source_for_admin.short_description = "source" + return format_html('{}', self.source, self.source) + @display(description="response to") def response_to_for_admin(self): - return format_html('{href}'.format(href=self.response_to)) - - response_to_for_admin.short_description = "response to" + return format_html('{}', self.response_to, self.response_to) def invalidate(self): if self.id: diff --git a/src/webmention/views.py b/src/webmention/views.py index 8b381b3..ff33ff3 100644 --- a/src/webmention/views.py +++ b/src/webmention/views.py @@ -27,7 +27,8 @@ def receive(request): webmention.update(source, target, response_body) return HttpResponse("The webmention was successfully received", status=202) except (SourceFetchError, TargetNotFoundError) as e: - webmention.invalidate() + if webmention: + webmention.invalidate() return HttpResponseBadRequest(str(e)) except Exception as e: return HttpResponseServerError(str(e)) diff --git a/tests/test_settings.py b/tests/settings.py similarity index 100% rename from tests/test_settings.py rename to tests/settings.py