diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..b6fcb12
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,64 @@
+name: Run CI
+
+# Run this workflow every time a new commit pushed to your repository
+on:
+ push:
+ branches:
+ - master
+ tags:
+ - '*'
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python: ['3.10', '3.11', '3.12']
+ django: ['3.2', '4.2']
+
+ name: Run the test suite (Python ${{ matrix.python }}, Django ${{ matrix.django }})
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python }}
+
+ - name: Install dependencies
+ run: pip install tox tox-gh-actions
+
+ - name: Run tests
+ run: tox
+ env:
+ PYTHON_VERSION: ${{ matrix.python }}
+ DJANGO: ${{ matrix.django }}
+
+ - name: Publish coverage report
+ uses: codecov/codecov-action@v3
+
+ 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@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: '3.10'
+
+ - name: Build sdist and wheel
+ run: |
+ pip install build --upgrade
+ python -m build
+
+ # TODO: switch to verified publishers
+ - name: Publish a Python distribution to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ user: __token__
+ password: ${{ secrets.PYPI_TOKEN }}
diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml
new file mode 100644
index 0000000..0cc5c96
--- /dev/null
+++ b/.github/workflows/code_quality.yml
@@ -0,0 +1,34 @@
+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, flake8, docs]
+ toxenv: [isort, black, flake8]
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: '3.10'
+ - name: Install dependencies
+ run: pip install tox
+ - run: tox
+ env:
+ TOXENV: ${{ matrix.toxenv }}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 37c136c..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-language: python
-python:
- - "3.5"
-
-services:
- - postgresql
-
-addons:
- postgresql: "9.4"
-
-install:
- - pip install codecov coveralls tox
-script:
- - tox --
-after_success:
- - coveralls
- - codecov
-env:
- - TOXENV=py27-django18
- - TOXENV=py34-django18
- - TOXENV=py35-django18
-
- - TOXENV=py27-django19
- - TOXENV=py34-django19
- - TOXENV=py35-django19
-
- - TOXENV=py27-django110
- - TOXENV=py34-django110
- - TOXENV=py35-django110
diff --git a/License b/LICENSE
similarity index 100%
rename from License
rename to LICENSE
diff --git a/MANIFEST.in b/MANIFEST.in
index ba979f0..aa032d0 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,3 +2,5 @@ include License
include README.rst
recursive-include mail_editor/templates *
recursive-include bin *
+global-exclude __pycache__
+global-exclude *.py[co]
diff --git a/README.rst b/README.rst
index 73ebd0a..0cf7fd9 100644
--- a/README.rst
+++ b/README.rst
@@ -1,18 +1,9 @@
MailEditor
==========
-.. image:: https://travis-ci.org/maykinmedia/mail-editor.svg?branch=master
- :target: https://travis-ci.org/maykinmedia/mail-editor
- :alt: Travis
.. image:: https://codecov.io/gh/maykinmedia/mail-editor/branch/develop/graph/badge.svg
:target: https://codecov.io/gh/maykinmedia/mail-editor
:alt: Coverage
-.. image:: https://lintly.com/gh/maykinmedia/mail-editor/badge.svg
- :target: https://lintly.com/gh/maykinmedia/mail-editor/
- :alt: Lintly
-.. image:: https://bettercodehub.com/edge/badge/maykinmedia/mail-editor?branch=master
- :target: https://bettercodehub.com/results/maykinmedia/mail-editor
- :alt: BCH compliance
.. image:: https://codeclimate.com/github/codeclimate/codeclimate/badges/gpa.svg
:target: https://codeclimate.com/github/codeclimate/codeclimate
:alt: Code Climate
@@ -39,10 +30,10 @@ For e-mail sending and logging, we recommend using a solution such as `Django Yu
This is only tested on a postgres database.
-Supported (read: Travis tested) are:
+Supported are:
-- python 2.7, 3.4, 3.5
-- Django 1.8, 1.9, 1.10, 1.11
+- python 3.10, 3.11, 3.12
+- Django 3.2, 4.2
- PostgreSQL
Warning
@@ -120,8 +111,8 @@ The following settings are an example:
MAIL_EDITOR_CONF = {
'activation': {
- 'name': ugettext_noop('Activation Email'),
- 'description': ugettext_noop('This email is used when people need to activate their account.'),
+ 'name': gettext_noop('Activation Email'),
+ 'description': gettext_noop('This email is used when people need to activate their account.'),
'subject_default': 'Activeer uw account voor {{site_name}}',
'body_default': """
Hallo {{ name }},
@@ -132,21 +123,21 @@ The following settings are an example:
""",
'subject': [{
'name': 'site_name',
- 'description': ugettext_noop('This is the name of the site. From the sites'),
- 'example': ugettext_noop('Example site'),
+ 'description': gettext_noop('This is the name of the site. From the sites'),
+ 'example': gettext_noop('Example site'),
}],
'body': [{
'name': 'name',
- 'description': ugettext_noop('This is the name of the user'),
- 'example': ugettext_noop('Jane Doe'),
+ 'description': gettext_noop('This is the name of the user'),
+ 'example': gettext_noop('Jane Doe'),
}, {
'name': 'site_name',
- 'description': ugettext_noop('This is the name of the site. From the sites'),
- 'example': ugettext_noop('Example site'),
+ 'description': gettext_noop('This is the name of the site. From the sites'),
+ 'example': gettext_noop('Example site'),
}, {
'name': 'activation_link',
- 'description': ugettext_noop('This is the link to activate their account.'),
- 'example': ugettext_noop('/'),
+ 'description': gettext_noop('This is the link to activate their account.'),
+ 'example': gettext_noop('/'),
}]
},
...
diff --git a/fix.sh b/fix.sh
index fdb2e72..6499bcf 100755
--- a/fix.sh
+++ b/fix.sh
@@ -7,3 +7,4 @@ black ./mail_editor
isort --profile black ./tests
autoflake --in-place --remove-all-unused-imports -r ./tests
black ./tests
+
diff --git a/mail_editor/mail_template.py b/mail_editor/mail_template.py
index d8fb3b1..5cf581f 100644
--- a/mail_editor/mail_template.py
+++ b/mail_editor/mail_template.py
@@ -2,8 +2,6 @@
Defines helpers for validating e-mail templates
"""
-from __future__ import absolute_import, unicode_literals
-
from django.core.exceptions import ValidationError
from django.template import ( # TODO: should be able to specify engine
Context,
@@ -63,7 +61,8 @@ def check_syntax_errors(self, value):
def check_variables(self, template, field):
variables_seen = set()
required_vars = {var.name for var in self.config[field] if var.required}
- optional_vars = {var.name for var in self.config[field] if not var.required}
+ # TODO do we need to check optional_vars? the following line was here but never used
+ # optional_vars = {var.name for var in self.config[field] if not var.required}
for node in template.nodelist.get_nodes_by_type(VariableNode):
var_name = node.filter_expression.var.var
variables_seen.add(var_name)
@@ -80,8 +79,8 @@ def check_variables(self, template, field):
params={field: message}, message=message, code=self.code
)
- def _is_attribute(self, vars, known_vars):
- for var in vars:
+ def _is_attribute(self, var_names, known_vars):
+ for var in var_names:
if any(var.startswith("{}.".format(known_var)) for known_var in known_vars):
return True
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..29da416
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,100 @@
+[build-system]
+requires = ["setuptools>=61.0.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "mail_editor"
+version = "0.3.6"
+description = "A Django package for email template editing"
+authors = [
+ {name = "Maykin Media", email = "support@maykinmedia.nl"}
+]
+readme = "README.rst"
+license = {file = "LICENSE"}
+keywords = ["TODO"]
+classifiers = [
+ "Development Status :: 3 - Alpha",
+ 'Environment :: Web Environment',
+ "Framework :: Django",
+ "Framework :: Django :: 3.2",
+ "Framework :: Django :: 4.2",
+ "Intended Audience :: Developers",
+ 'License :: OSI Approved :: BSD License',
+ "Operating System :: Unix",
+ "Operating System :: MacOS",
+ "Operating System :: Microsoft :: Windows",
+ 'Programming Language :: Python',
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+ "Topic :: Software Development :: Libraries :: Python Modules",
+]
+requires-python = ">=3.10"
+dependencies = [
+ "django>=3.2",
+ "django-choices",
+ "django-ckeditor",
+ "requests",
+ "lxml",
+ "css_inline"
+]
+
+[project.urls]
+Homepage = "https://github.com/maykinmedia/mail-editor"
+Documentation = "https://github.com/maykinmedia/mail-editor"
+"Bug Tracker" = "https://github.com/maykinmedia/mail-editor/issues"
+"Source Code" = "https://github.com/maykinmedia/mail-editor"
+
+[project.optional-dependencies]
+tests = [
+ "pytest",
+ "pytest-django",
+ "tox",
+ "isort",
+ "black",
+ "flake8",
+ "autoflake",
+ "django_webtest",
+]
+coverage = [
+ "pytest-cov",
+]
+release = [
+ "bump-my-version",
+ "twine",
+]
+
+[tool.setuptools.packages.find]
+include = ["mail_editor*"]
+namespaces = false
+
+[tool.isort]
+profile = "black"
+combine_as_imports = true
+known_django = "django"
+known_first_party="mail_editor"
+sections=["FUTURE", "STDLIB", "DJANGO", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+DJANGO_SETTINGS_MODULE = "tests.settings"
+
+[tool.bumpversion]
+current_version = "0.3.5"
+files = [
+ {filename = "pyproject.toml"},
+ {filename = "README.rst"},
+]
+
+[tool.coverage.report]
+exclude_also = [
+ "if (typing\\.)?TYPE_CHECKING:",
+ "@(typing\\.)?overload",
+ "class .*\\(.*Protocol.*\\):",
+ "@(abc\\.)?abstractmethod",
+ "raise NotImplementedError",
+ "\\.\\.\\.",
+ "pass",
+]
diff --git a/setup.cfg b/setup.cfg
index 288390b..44f7e3b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -6,26 +6,12 @@ universal=1
[pep8]
ignore=W293,W291,E501,E261
-
max-line-length=120
-
exclude=migrations,static,media
-[isort]
-combine_as_imports = true
-default_section = THIRDPARTY
-include_trailing_comma = false
-line_length = 79
-multi_line_output = 5
-skip = env,node_modules
-skip_glob = **/migrations/**
-not_skip = __init__.py
-known_django=django
-known_first_party=mail_editor
-sections=FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
-
-[tool:pytest]
-django_find_project=false
-DJANGO_SETTINGS_MODULE=tests.settings
+[flake8]
+ignore=E203,E261,E501,E731,F405,W293,W291,W503,F841,E741
+max-line-length=120
+exclude=env,.tox
python_paths=.
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 1d58515..0000000
--- a/setup.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import os
-
-from setuptools import find_packages, setup
-
-# allow setup.py to be run from any path
-os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
-
-setup(
- name='mail_editor',
- version='0.3.5',
- license='BSD',
-
- # packaging
- install_requires=[
- 'Django>=1.8',
- 'django-choices',
- 'django-ckeditor',
- 'requests',
- 'lxml',
- 'css_inline',
- ],
- include_package_data=True,
- scripts=[],
- packages=find_packages(exclude=["tests"]),
-
- # tests
- setup_requires=['pytest-runner'],
- tests_require=[
- 'factory-boy',
- 'psycopg2',
- 'pytest',
- 'pytest-cov',
- 'pytest-django',
- 'pytest-pep8',
- 'pytest-pylint',
- 'pytest-pythonpath',
- 'django-webtest',
- ],
-
- # metadata
- description='A Django package for email template editing',
- url='https://github.com/maykinmedia/mail_editor',
- author='Sergei Maertens, Maykin Media, Jorik Kraaikamp',
- author_email='sergei@maykinmedia.nl',
- classifiers=[
- 'Development Status :: 3 - Alpha',
- 'Environment :: Web Environment',
- 'Framework :: Django',
- 'Framework :: Django :: 1.8',
- 'Framework :: Django :: 1.9',
- 'Framework :: Django :: 1.10',
- 'Intended Audience :: Developers',
- 'License :: OSI Approved :: BSD License',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.3',
- 'Programming Language :: Python :: 3.4',
- 'Programming Language :: Python :: 3.5',
- 'Topic :: Internet :: WWW/HTTP',
- 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
- ],
-)
diff --git a/tests/test_send.py b/tests/test_send.py
index b227d46..96a923f 100644
--- a/tests/test_send.py
+++ b/tests/test_send.py
@@ -3,7 +3,7 @@
from django.core import mail
from django.test import TestCase, override_settings
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from mail_editor.helpers import find_template
diff --git a/tests/test_template_rendering.py b/tests/test_template_rendering.py
index a3546ac..edcee0c 100644
--- a/tests/test_template_rendering.py
+++ b/tests/test_template_rendering.py
@@ -41,7 +41,7 @@ def test_simple(self):
subject, body = template.render(body_context, subj_context=subject_context)
- self.assertEquals(subject, "Important message for 111")
+ self.assertEqual(subject, "Important message for 111")
self.assertIn("Test mail sent from testcase with 111", body)
@override_settings(MAIL_EDITOR_CONF=CONFIG, MAIL_EDITOR_BASE_CONTEXT={"id": "BASE"})
@@ -53,7 +53,7 @@ def test_base_context(self):
subject, body = template.render(body_context, subj_context=subject_context)
- self.assertEquals(subject, "Important message for BASE")
+ self.assertEqual(subject, "Important message for BASE")
self.assertIn("Test mail sent from testcase with BASE", body)
@override_settings(
@@ -68,7 +68,7 @@ def test_dynamic_context(self):
subject, body = template.render(body_context, subj_context=subject_context)
- self.assertEquals(subject, "Important message for DYNAMIC")
+ self.assertEqual(subject, "Important message for DYNAMIC")
self.assertIn("Test mail sent from testcase with DYNAMIC", body)
@override_settings(MAIL_EDITOR_CONF=CONFIG)
@@ -81,7 +81,7 @@ def test_incorrect_base_path(self):
subject, body = template.render(body_context, subj_context=subject_context)
- self.assertEquals(subject, "Important message for 222")
+ self.assertEqual(subject, "Important message for 222")
self.assertIn("Test mail sent from testcase with 222", body)
@override_settings(MAIL_EDITOR_CONF=CONFIG)
@@ -98,7 +98,7 @@ def test_base_template_errors(self):
subject, body = template.render(body_context, subj_context=subject_context)
- self.assertEquals(subject, "Important message for 333")
+ self.assertEqual(subject, "Important message for 333")
self.assertIn("Test mail sent from testcase with 333", body)
@override_settings(MAIL_EDITOR_CONF=CONFIG)
@@ -110,6 +110,6 @@ def test_render_preview(self):
subject, body = template.render(body_context, subj_context=subject_context)
# rendered placeholder
- self.assertEquals(subject, "Important message for --id--")
+ self.assertEqual(subject, "Important message for --id--")
# rendered example
self.assertIn("Test mail sent from testcase with 321", body)
diff --git a/tox.ini b/tox.ini
index 8b24d8d..fd3f690 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,34 +1,48 @@
[tox]
-envlist = py{27,34,35,py}-django{18,19,110},isort
+envlist =
+ py{310,311,312}-django{32,42}
+ isort
+ black
+ flake8
skip_missing_interpreters = true
+[gh-actions]
+python =
+ 3.10: py310
+ 3.11: py311
+ 3.12: py312
+
+[gh-actions:env]
+DJANGO =
+ 3.2: django32
+ 4.2: django42
+
[testenv]
+setenv =
+ DJANGO_SETTINGS_MODULE=tests.settings
+ PYTHONPATH={toxinidir}
+extras =
+ tests
+ coverage
deps =
- factory-boy
- mock
- psycopg2
- pytest
- pytest-cov
- pytest-django
- pytest-pep8
- pytest-pylint
- pytest-pythonpath
- pytest-runner
- django18: Django>=1.8,<1.9
- django19: Django>=1.9,<1.10
- django110: Django>=1.10,<1.11
-commands=
- py.test \
- --cov-report=xml \
- --cov=mail_editor \
- --verbose \
- --junit-xml=junit.xml \
- --color=yes \
- tests \
- {posargs}
-
+ django42: Django~=4.2.0
+ django32: Django~=3.2.0
+commands =
+ py.test tests \
+ --cov --cov-report xml:reports/coverage-{envname}.xml \
+ {posargs}
[testenv:isort]
-deps = isort
+extras = tests
+skipsdist = True
+commands = isort --check-only --diff .
+
+[testenv:black]
+extras = tests
+skipsdist = True
+commands = black --check mail_editor tests
+
+[testenv:flake8]
+extras = tests
skipsdist = True
-commands = isort --recursive --check-only --diff mail_editor
+commands = flake8 .