diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..fb049935 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,53 @@ +name: Release + +on: + push: + tags: + - '*' + +jobs: + build: + if: github.repository == 'jazzband/django-newsletter' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: release-${{ hashFiles('**/setup.py') }}-${{ hashFiles('**/tox.ini') }} + restore-keys: | + release- + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U setuptools twine wheel + + - name: Build package + run: | + python setup.py --version + python setup.py sdist --format=gztar bdist_wheel + twine check dist/* + + - name: Upload packages to Jazzband + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@master + with: + user: jazzband + password: ${{ secrets.JAZZBAND_RELEASE_KEY }} + repository_url: https://jazzband.co/projects/django-newsletter/upload diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..6765bee9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,52 @@ +name: Test + +on: [push, pull_request] + +jobs: + build: + name: build (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 5 + matrix: + python-version: ['3.6', '3.7', '3.8', '3.9'] + django-version: ['2.2', '3.0', '3.1', 'main'] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + ${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}-${{ hashFiles('**/tox.ini') }} + restore-keys: | + ${{ matrix.python-version }}-v1- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade tox tox-gh-actions + + - name: Tox tests + run: | + tox -v + env: + DJANGO: ${{ matrix.django-version }} + + - name: Upload coverage + uses: codecov/codecov-action@v1 + with: + name: Python ${{ matrix.python-version }} diff --git a/.gitignore b/.gitignore index bea050bb..089a1a33 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ django_setuptest* docs/_* .idea/ venv/ +coverage.xml +.tox diff --git a/.landscape.yml b/.landscape.yml deleted file mode 100644 index ed35b527..00000000 --- a/.landscape.yml +++ /dev/null @@ -1,2 +0,0 @@ -ignore-paths: - - newsletter/south_migrations diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4dae500f..00000000 --- a/.travis.yml +++ /dev/null @@ -1,41 +0,0 @@ -os: linux - -language: python - -python: - - 3.5 - - 3.6 - - 3.7 - - 3.8 - -env: - - DJANGO="Django<2.3" # Django 2.2.x LTS - - DJANGO="Django<3.1" # Django 3.0.x - - DJANGO="Django<3.2" # Django 3.1.x - -cache: - directories: - - $HOME/.cache/pip - -matrix: - exclude: - # Django 3.0 and 3.1 don't support Python 3.5 - - env: DJANGO="Django<3.1" - python: 3.5 - - env: DJANGO="Django<3.2" - python: 3.5 - -# Command to install dependencies -install: - # Latest PIP uses wheel by default - - pip install --upgrade pip - - pip install $DJANGO - - pip install -r requirements.txt - - pip install -r requirements_test.txt - - pip install coveralls - -# Command to run tests -script: coverage run setup.py test - -after_success: - coveralls diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index af7b03e8..142f8db2 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -13,6 +13,6 @@ Thanks for your awesome contrib! If you'd like to have your code included in mas couple of things you have to take care of though: 1. Ensure that the way you implemented the functionality is generic enough for other users to make use of and does not degrade the performance of existing users. If you unsure about this, create an issue with proposed functionality to discuss with the collaborators first. -2. Make sure the tests are passing. In any case, Travis should report passing tests. -3. Extended tests to cover any additional code included in your commit. In any case, the coveralls report should report equal or increased coverage. +2. Make sure the tests are passing. In any case, GitHub Actions should report passing tests. +3. Extended tests to cover any additional code included in your commit. In any case, the Codecov report should report equal or increased coverage. 4. Make sure that any added or changed functionality is documented in the Sphinx documentation. diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 7ed6d44c..00000000 --- a/Pipfile +++ /dev/null @@ -1,24 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -django-imperavi = "*" -django-tinymce = "*" -pytz = "*" -django-webtest = "*" -WebTest = "*" -python-card-me = "<1.0" -ldif3 = "<3.2" -chardet = "*" -surlex = ">=0.2.0" -sorl-thumbnail = ">=12.6.3" -unicodecsv = "<0.15" -Django = ">=2.2.16" -Pillow = ">=6.2.2" - -[dev-packages] -coverage = "*" -transifex-client = "*" -twine = "*" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 5506ad91..00000000 --- a/Pipfile.lock +++ /dev/null @@ -1,451 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "c88397d77e5b23467f123b4884212deafc9bc3022cbf2f4b96d7c7fa33b546b4" - }, - "pipfile-spec": 6, - "requires": {}, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "asgiref": { - "hashes": [ - "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a", - "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed" - ], - "version": "==3.2.10" - }, - "beautifulsoup4": { - "hashes": [ - "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35", - "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25", - "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666" - ], - "version": "==4.9.3" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "index": "pypi", - "version": "==3.0.4" - }, - "django": { - "hashes": [ - "sha256:a2127ad0150ec6966655bedf15dbbff9697cc86d61653db2da1afa506c0b04cc", - "sha256:c93c28ccf1d094cbd00d860e83128a39e45d2c571d3b54361713aaaf9a94cac4" - ], - "index": "pypi", - "version": "==3.1.2" - }, - "django-imperavi": { - "hashes": [ - "sha256:2b05ff0afbc8a23a28ab599abef6aa8e762f32b5830ce7522905b471763d3da2" - ], - "index": "pypi", - "version": "==0.2.3" - }, - "django-tinymce": { - "hashes": [ - "sha256:47db20515d159c69e3b8c69dca73adfb4e60010335250514d9f6fce6dc98c85c", - "sha256:d3a641ec0d10db05dacab7bf9b11e9c8114d017ad1a9132ba18b10ab6bff1934" - ], - "index": "pypi", - "version": "==3.1.0" - }, - "django-webtest": { - "hashes": [ - "sha256:b9b4b94670c0ce533efc456d02dd55a0d0a7a8f7912eb30728dca2d59d7948b4", - "sha256:c5a1e486a3d8d3623aa615b6db2f27de848aa7079303a84721e9a685f839796c" - ], - "index": "pypi", - "version": "==1.9.7" - }, - "ldif3": { - "hashes": [ - "sha256:ccdf6ac2ed3f88912b7509529694c0e289c4392d34f2095cca22fa6d70ac189a" - ], - "index": "pypi", - "version": "==3.1.1" - }, - "mock": { - "hashes": [ - "sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0", - "sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72" - ], - "index": "pypi", - "version": "==4.0.2" - }, - "pillow": { - "hashes": [ - "sha256:006de60d7580d81f4a1a7e9f0173dc90a932e3905cc4d47ea909bc946302311a", - "sha256:0a2e8d03787ec7ad71dc18aec9367c946ef8ef50e1e78c71f743bc3a770f9fae", - "sha256:0eeeae397e5a79dc088d8297a4c2c6f901f8fb30db47795113a4a605d0f1e5ce", - "sha256:11c5c6e9b02c9dac08af04f093eb5a2f84857df70a7d4a6a6ad461aca803fb9e", - "sha256:2fb113757a369a6cdb189f8df3226e995acfed0a8919a72416626af1a0a71140", - "sha256:4b0ef2470c4979e345e4e0cc1bbac65fda11d0d7b789dbac035e4c6ce3f98adb", - "sha256:59e903ca800c8cfd1ebe482349ec7c35687b95e98cefae213e271c8c7fffa021", - "sha256:5abd653a23c35d980b332bc0431d39663b1709d64142e3652890df4c9b6970f6", - "sha256:5f9403af9c790cc18411ea398a6950ee2def2a830ad0cfe6dc9122e6d528b302", - "sha256:6b4a8fd632b4ebee28282a9fef4c341835a1aa8671e2770b6f89adc8e8c2703c", - "sha256:6c1aca8231625115104a06e4389fcd9ec88f0c9befbabd80dc206c35561be271", - "sha256:795e91a60f291e75de2e20e6bdd67770f793c8605b553cb6e4387ce0cb302e09", - "sha256:7ba0ba61252ab23052e642abdb17fd08fdcfdbbf3b74c969a30c58ac1ade7cd3", - "sha256:7c9401e68730d6c4245b8e361d3d13e1035cbc94db86b49dc7da8bec235d0015", - "sha256:81f812d8f5e8a09b246515fac141e9d10113229bc33ea073fec11403b016bcf3", - "sha256:895d54c0ddc78a478c80f9c438579ac15f3e27bf442c2a9aa74d41d0e4d12544", - "sha256:8de332053707c80963b589b22f8e0229f1be1f3ca862a932c1bcd48dafb18dd8", - "sha256:92c882b70a40c79de9f5294dc99390671e07fc0b0113d472cbea3fde15db1792", - "sha256:95edb1ed513e68bddc2aee3de66ceaf743590bf16c023fb9977adc4be15bd3f0", - "sha256:b63d4ff734263ae4ce6593798bcfee6dbfb00523c82753a3a03cbc05555a9cc3", - "sha256:bd7bf289e05470b1bc74889d1466d9ad4a56d201f24397557b6f65c24a6844b8", - "sha256:cc3ea6b23954da84dbee8025c616040d9aa5eaf34ea6895a0a762ee9d3e12e11", - "sha256:cc9ec588c6ef3a1325fa032ec14d97b7309db493782ea8c304666fb10c3bd9a7", - "sha256:d3d07c86d4efa1facdf32aa878bd508c0dc4f87c48125cc16b937baa4e5b5e11", - "sha256:d8a96747df78cda35980905bf26e72960cba6d355ace4780d4bdde3b217cdf1e", - "sha256:e38d58d9138ef972fceb7aeec4be02e3f01d383723965bfcef14d174c8ccd039", - "sha256:eb472586374dc66b31e36e14720747595c2b265ae962987261f044e5cce644b5", - "sha256:fbd922f702582cb0d71ef94442bfca57624352622d75e3be7a1e7e9360b07e72" - ], - "index": "pypi", - "version": "==8.0.1" - }, - "python-card-me": { - "hashes": [ - "sha256:37ca483a574534177227152123b1e1381330c75a62c4d4d60cd2eeb188384bf2" - ], - "index": "pypi", - "version": "==0.9.3" - }, - "python-dateutil": { - "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" - ], - "version": "==2.8.1" - }, - "pytz": { - "hashes": [ - "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", - "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" - ], - "index": "pypi", - "version": "==2020.1" - }, - "six": { - "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" - ], - "index": "pypi", - "version": "==1.15.0" - }, - "sorl-thumbnail": { - "hashes": [ - "sha256:66771521f3c0ed771e1ce8e1aaf1639ebff18f7f5a40cfd3083da8f0fe6c7c99", - "sha256:7162639057dff222a651bacbdb6bd6f558fc32946531d541fc71e10c0167ebdf" - ], - "index": "pypi", - "version": "==12.6.3" - }, - "soupsieve": { - "hashes": [ - "sha256:1634eea42ab371d3d346309b93df7870a88610f0725d47528be902a0d95ecc55", - "sha256:a59dc181727e95d25f781f0eb4fd1825ff45590ec8ff49eadfd7f1a537cc0232" - ], - "markers": "python_version >= '3.0'", - "version": "==2.0.1" - }, - "sqlparse": { - "hashes": [ - "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", - "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" - ], - "version": "==0.4.1" - }, - "surlex": { - "hashes": [ - "sha256:62057b52d147bf83aec36205a42a0080e0a0de4d6c8b8d6ae56adf477253b2df" - ], - "index": "pypi", - "version": "==0.2.0" - }, - "unicodecsv": { - "hashes": [ - "sha256:018c08037d48649a0412063ff4eda26eaa81eff1546dbffa51fa5293276ff7fc" - ], - "index": "pypi", - "version": "==0.14.1" - }, - "waitress": { - "hashes": [ - "sha256:1bb436508a7487ac6cb097ae7a7fe5413aefca610550baf58f0940e51ecfb261", - "sha256:3d633e78149eb83b60a07dfabb35579c29aac2d24bb803c18b26fb2ab1a584db" - ], - "version": "==1.4.4" - }, - "webob": { - "hashes": [ - "sha256:a3c89a8e9ba0aeb17382836cdb73c516d0ecf6630ec40ec28288f3ed459ce87b", - "sha256:aa3a917ed752ba3e0b242234b2a373f9c4e2a75d35291dcbe977649bd21fd108" - ], - "version": "==1.8.6" - }, - "webtest": { - "hashes": [ - "sha256:44ddfe99b5eca4cf07675e7222c81dd624d22f9a26035d2b93dc8862dc1153c6", - "sha256:aac168b5b2b4f200af4e35867cf316712210e3d5db81c1cbdff38722647bb087" - ], - "index": "pypi", - "version": "==2.0.35" - } - }, - "develop": { - "bleach": { - "hashes": [ - "sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080", - "sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd" - ], - "version": "==3.2.1" - }, - "certifi": { - "hashes": [ - "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", - "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" - ], - "version": "==2020.6.20" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "index": "pypi", - "version": "==3.0.4" - }, - "colorama": { - "hashes": [ - "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", - "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" - ], - "version": "==0.4.4" - }, - "coverage": { - "hashes": [ - "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516", - "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259", - "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9", - "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097", - "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0", - "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f", - "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7", - "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c", - "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5", - "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7", - "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729", - "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978", - "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9", - "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f", - "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9", - "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822", - "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418", - "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82", - "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f", - "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d", - "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221", - "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4", - "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21", - "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709", - "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54", - "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d", - "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270", - "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24", - "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751", - "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a", - "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237", - "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7", - "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636", - "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8" - ], - "index": "pypi", - "version": "==5.3" - }, - "docutils": { - "hashes": [ - "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", - "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" - ], - "version": "==0.16" - }, - "gitdb": { - "hashes": [ - "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac", - "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9" - ], - "version": "==4.0.5" - }, - "gitpython": { - "hashes": [ - "sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b", - "sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8" - ], - "version": "==3.1.11" - }, - "idna": { - "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" - ], - "version": "==2.10" - }, - "importlib-metadata": { - "hashes": [ - "sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da", - "sha256:cefa1a2f919b866c5beb7c9f7b0ebb4061f30a8a9bf16d609b000e2dfaceb9c3" - ], - "markers": "python_version < '3.8'", - "version": "==2.0.0" - }, - "keyring": { - "hashes": [ - "sha256:4e34ea2fdec90c1c43d6610b5a5fafa1b9097db1802948e90caf5763974b8f8d", - "sha256:9aeadd006a852b78f4b4ef7c7556c2774d2432bbef8ee538a3e9089ac8b11466" - ], - "version": "==21.4.0" - }, - "packaging": { - "hashes": [ - "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", - "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" - ], - "version": "==20.4" - }, - "pkginfo": { - "hashes": [ - "sha256:78d032b5888ec06d7f9d18fbf8c0549a6a3477081b34cb769119a07183624fc1", - "sha256:dd008e95b13141ddd05d7e8881f0c0366a998ab90b25c2db794a1714b71583cc" - ], - "version": "==1.6.0" - }, - "pygments": { - "hashes": [ - "sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0", - "sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773" - ], - "version": "==2.7.2" - }, - "pyparsing": { - "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" - ], - "version": "==2.4.7" - }, - "python-slugify": { - "hashes": [ - "sha256:69a517766e00c1268e5bbfc0d010a0a8508de0b18d30ad5a1ff357f8ae724270" - ], - "version": "==4.0.1" - }, - "readme-renderer": { - "hashes": [ - "sha256:267854ac3b1530633c2394ead828afcd060fc273217c42ac36b6be9c42cd9a9d", - "sha256:6b7e5aa59210a40de72eb79931491eaf46fefca2952b9181268bd7c7c65c260a" - ], - "version": "==28.0" - }, - "requests": { - "hashes": [ - "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", - "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" - ], - "version": "==2.24.0" - }, - "requests-toolbelt": { - "hashes": [ - "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f", - "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0" - ], - "version": "==0.9.1" - }, - "rfc3986": { - "hashes": [ - "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d", - "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50" - ], - "version": "==1.4.0" - }, - "six": { - "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" - ], - "index": "pypi", - "version": "==1.15.0" - }, - "smmap": { - "hashes": [ - "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4", - "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24" - ], - "version": "==3.0.4" - }, - "text-unidecode": { - "hashes": [ - "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", - "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" - ], - "version": "==1.3" - }, - "tqdm": { - "hashes": [ - "sha256:43ca183da3367578ebf2f1c2e3111d51ea161ed1dc4e6345b86e27c2a93beff7", - "sha256:69dfa6714dee976e2425a9aab84b622675b7b1742873041e3db8a8e86132a4af" - ], - "version": "==4.50.2" - }, - "transifex-client": { - "hashes": [ - "sha256:a8a06330acb97403b24153fb51c2c6ae5c8ab0a989fed06f8a27ce70323c7d5e" - ], - "index": "pypi", - "version": "==0.14.1" - }, - "twine": { - "hashes": [ - "sha256:34352fd52ec3b9d29837e6072d5a2a7c6fe4290e97bba46bb8d478b5c598f7ab", - "sha256:ba9ff477b8d6de0c89dd450e70b2185da190514e91c42cc62f96850025c10472" - ], - "index": "pypi", - "version": "==3.2.0" - }, - "urllib3": { - "hashes": [ - "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2", - "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e" - ], - "version": "==1.25.11" - }, - "webencodings": { - "hashes": [ - "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", - "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" - ], - "version": "==0.5.1" - }, - "zipp": { - "hashes": [ - "sha256:50a4ef266c31c9409627b46012e206382eb8d23f46fbd358065a8335cfbf7d8f", - "sha256:adf8f2ed8f614ced567d849cae9d183cef6cfd27c77a5cae7a28029be0c2b7a7" - ], - "version": "==3.3.2" - } - } -} diff --git a/README.rst b/README.rst index 87d90c4d..d7582832 100644 --- a/README.rst +++ b/README.rst @@ -5,11 +5,12 @@ django-newsletter .. image:: https://img.shields.io/pypi/v/django-newsletter.svg :target: https://pypi.python.org/pypi/django-newsletter -.. image:: https://img.shields.io/travis/jazzband/django-newsletter/master.svg - :target: http://travis-ci.org/jazzband/django-newsletter +.. image:: https://github.com/jazzband/django-newsletter/workflows/Test/badge.svg + :target: https://github.com/jazzband/django-newsletter/actions + :alt: GitHub Actions -.. image:: https://coveralls.io/repos/github/jazzband/django-newsletter/badge.svg?branch=master - :target: https://coveralls.io/github/jazzband/django-newsletter?branch=master +.. image:: https://codecov.io/gh/jazzband/django-newsletter/branch/master/graph/badge.svg + :target: https://codecov.io/gh/jazzband/django-newsletter .. image:: https://jazzband.co/static/img/badge.svg :target: https://jazzband.co/ @@ -58,7 +59,7 @@ Fairly extensive tests are available for internal frameworks, web (un)subscription and mail sending. Sending a newsletter to large groups of recipients (+15k) has been confirmed to work in multiple production environments. Tests for pull req's and the master branch are automatically run through -`Travis CI `_. +`GitHub Actions `_. Contributing ============= diff --git a/docs/conf.py b/docs/conf.py index 45ffb757..0d70b417 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,7 +11,10 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys +from pkg_resources import get_distribution + # Determine whether rendering on RTD on_rtd = os.environ.get('READTHEDOCS', None) == 'True' @@ -80,10 +83,9 @@ # |version| and |release|, also used in various other places throughout the # built documents. # -# The short X.Y version. -version = '0.5' -# The full version, including alpha/beta/rc tags. -release = '0.5.1' +release = get_distribution('django-newsletter').version +# for example take major/minor +version = '.'.join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/newsletter/__init__.py b/newsletter/__init__.py index e69de29b..6b350941 100644 --- a/newsletter/__init__.py +++ b/newsletter/__init__.py @@ -0,0 +1,7 @@ +from pkg_resources import get_distribution, DistributionNotFound + +try: + __version__ = get_distribution("django-newsletter").version +except DistributionNotFound: + # package is not installed + __version__ = None diff --git a/newsletter/admin_utils.py b/newsletter/admin_utils.py index 18b9a7e5..51299075 100644 --- a/newsletter/admin_utils.py +++ b/newsletter/admin_utils.py @@ -2,7 +2,7 @@ from django.contrib.admin.utils import unquote from django.http import Http404 -from django.utils.encoding import force_text +from django.utils.encoding import force_str from django.utils.translation import gettext as _ from .models import Subscription @@ -25,8 +25,8 @@ def _getobj(self, request, object_id): '%(name)s object with primary key ' '\'%(key)s\' does not exist.' ) % { - 'name': force_text(opts.verbose_name), - 'key': force_text(object_id) + 'name': force_str(opts.verbose_name), + 'key': force_str(object_id) } ) diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index fd00206b..00000000 --- a/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -Django>=2.2.16 -python-card-me<1.0 -ldif3<3.2 -chardet -unicodecsv<0.15 -Pillow diff --git a/requirements_test.txt b/requirements_test.txt deleted file mode 100644 index 5aacc714..00000000 --- a/requirements_test.txt +++ /dev/null @@ -1,8 +0,0 @@ -django-imperavi -# TinyMCE above 3 doesn't support Python 3.5 anymore. -# TODO: Remove version freeze when Django 2.2 LTS support is dropped, early 2022. -django-tinymce<3 -pytz -webtest -django-webtest -mock diff --git a/runtests.py b/runtests.py deleted file mode 100755 index e000c087..00000000 --- a/runtests.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python - -import os -import sys - -import django -from django.core.management.commands.test import Command as TestCommand - - -def setup_django(): - """ Setup Django for testing using the test_project directory """ - - test_project_dir = os.path.join(os.path.dirname(__file__), 'test_project') - sys.path.insert(0, test_project_dir) - - os.environ['DJANGO_SETTINGS_MODULE'] = 'test_project.settings' - django.setup() - - -def run_tests(): - """ Run tests via setuptools, all tests with no special options """ - - setup_django() - - # Bypass argument parsing and run the test command manually with minimal args - TestCommand().handle(**{'testrunner': None, 'liveserver': None}) - - sys.exit(0) # TestCommand exits itself on failure, we only exit on success - - -if __name__ == "__main__": - setup_django() - - # Command expects to be called via 'manage.py test' so - # add the extra argument so that it can parse correctly - sys.argv.insert(1, 'test') - - # Run the test command with argv to get all the argument goodies - TestCommand().run_from_argv(sys.argv) diff --git a/setup.py b/setup.py index 07901186..de773439 100755 --- a/setup.py +++ b/setup.py @@ -30,21 +30,11 @@ warnings.warn('Could not read README.rst and/or CHANGES.rst') README = None -try: - REQUIREMENTS = open('requirements.txt').read() -except: - warnings.warn('Could not read requirements.txt') - REQUIREMENTS = None - -try: - TEST_REQUIREMENTS = open('requirements_test.txt').read() -except: - warnings.warn('Could not read requirements_test.txt') - TEST_REQUIREMENTS = None setup( name='django-newsletter', - version="1.0b1", + use_scm_version={"version_scheme": "post-release"}, + setup_requires=["setuptools_scm"], description=( 'Django app for managing multiple mass-mailing lists with both ' 'plaintext as well as HTML templates (and pluggable WYSIWYG editors ' @@ -52,7 +42,14 @@ 'the admin interface.' ), long_description=README, - install_requires=REQUIREMENTS, + install_requires=[ + "Django>=2.2.16", + "python-card-me<1.0", + "ldif3<3.2", + "chardet", + "unicodecsv<0.15", + "Pillow", + ], author='Mathijs de Bruin', author_email='mathijs@mathijsfietst.nl', url='http://github.com/jazzband/django-newsletter/', @@ -66,12 +63,11 @@ 'License :: OSI Approved :: GNU Affero General Public License v3', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Topic :: Utilities' ], - test_suite='runtests.run_tests', - tests_require=TEST_REQUIREMENTS ) diff --git a/tests/test_admin.py b/tests/test_admin.py index 38431594..6ae80fbf 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -13,8 +13,6 @@ from newsletter.admin_utils import make_subscription from newsletter.models import Message, Newsletter, Submission, Subscription, Attachment, attachment_upload_to -from .utils import AssertLogsMixin - test_files_dir = os.path.join(os.path.dirname(__file__), 'files') @@ -42,7 +40,7 @@ def setUp(self): message=self.message_with_attachment) -class AdminTestCase(AdminTestMixin, AssertLogsMixin, TestCase): +class AdminTestCase(AdminTestMixin, TestCase): def admin_import_file(self, source_file, ignore_errors=''): """ Upload an address file for import to admin. """ diff --git a/tests/test_web.py b/tests/test_web.py index 33fc058f..80fe25cf 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -9,7 +9,7 @@ from django.core import mail from django.contrib.auth import get_user_model from django.utils import timezone -from django.utils.encoding import force_text +from django.utils.encoding import force_str from django.test.utils import override_settings from django.urls import reverse @@ -458,7 +458,7 @@ def test_unsubscribe_not_subscribed_view(self): self.assertIn( 'You are not subscribed to', - force_text(list(response.context['messages'])[0]) + force_str(list(response.context['messages'])[0]) ) def test_unsubscribe_post(self): diff --git a/tests/utils.py b/tests/utils.py index 450f921f..76dd8904 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -12,69 +12,11 @@ from django.test import TestCase - from django.template import loader, TemplateDoesNotExist from django_webtest import WebTest -class AssertLogsMixin: - """Mixin to enable assertLogs method in Python 2.7. - - patch_logger has similar functionality for Python 2.7, but is - removed in Django 3.0. This mixin reworks patch_logger so that - the assertLogs method can be used for any supported Django - version. - """ - # TODO: Remove use of mixin when Django 1.11 support dropped - def assertLogs(self, logger=None, level=None): - # Use assertLogs context manager if present - try: - from unittest.case import _AssertLogsContext - - return _AssertLogsContext(self, logger, level) - except ImportError: - # Fallback if Django version does not support assertLogs - import logging - from django.test.utils import patch_logger - - class PatchLoggerResponse: - """Object to mimic AssertLogsContext response.""" - def __init__(self, messages): - self.output = messages - - @contextmanager - def patch_logger(logger_name, log_level, log_kwargs=False): - """Replicating patch_logger functionality from Django 1.11. - - Cannot use original Django patch_logger because of how - it returns its response. Have copied and modified it - to return an object that mimics the assertLogs response. - """ - logger_response = PatchLoggerResponse([]) - - def replacement(msg, *args, **kwargs): - call = msg % args - logger_response.output.append((call, kwargs) if log_kwargs else call) - - logger = logging.getLogger(logger_name) - orig = getattr(logger, log_level) - setattr(logger, log_level, replacement) - - try: - yield logger_response - finally: - setattr(logger, log_level, orig) - - if len(logger_response.output) == 0: - raise self.failureException( - "no logs of level {} or higher triggered on {}".format( - log_level, logger_name - ) - ) - - return patch_logger(logger, level.lower()) - class WebTestCase(WebTest): def setUp(self): self.site = Site.objects.get_current() @@ -98,7 +40,7 @@ def assertInContext(self, response, variable, self.assertEqual(instance, value) -class MailTestCase(AssertLogsMixin, TestCase): +class MailTestCase(TestCase): def get_email_list(self, email): if email: return (email,) @@ -160,7 +102,7 @@ def assertEmailHasHeader(self, header, content=None, email=None): self.assertEqual(my_email.extra_headers[header], content) -class UserTestCase(AssertLogsMixin, TestCase): +class UserTestCase(TestCase): def setUp(self): super().setUp() @@ -185,7 +127,7 @@ def tearDown(self): self.user.delete() -class ComparingTestCase(AssertLogsMixin, TestCase): +class ComparingTestCase(TestCase): def assertLessThan(self, value1, value2): self.assertTrue(value1 < value2) diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..b32407cc --- /dev/null +++ b/tox.ini @@ -0,0 +1,44 @@ +[tox] +envlist = + py{36,37,38,39}-dj{22,30,31} + py{38,39}-djmain + +[testenv] +deps = + coverage + django-imperavi + # TinyMCE above 3 doesn't support Python 3.5 anymore. + # TODO: Remove version freeze when Django 2.2 LTS support is dropped, early 2022. + django-tinymce<3 + pytz + webtest + django-webtest + mock + dj22: Django>=2.2,<3.0 + dj30: Django>=3.0,<3.1 + dj31: Django>=3.1,<3.2 + djmain: https://github.com/django/django/archive/main.tar.gz +usedevelop = True +ignore_outcome = + djmain: True +commands = + coverage run {envbindir}/django-admin test + coverage report + coverage xml +setenv = + DJANGO_SETTINGS_MODULE=test_project.settings + PYTHONPATH={toxinidir}/test_project + +[gh-actions] +python = + 3.6: py36 + 3.7: py37 + 3.8: py38 + 3.9: py39 + +[gh-actions:env] +DJANGO = + 2.2: dj22 + 3.0: dj30 + 3.1: dj31 + main: djmain