diff -Nru python-django-otp-0.7.5/.bumpversion.cfg python-django-otp-0.8.1/.bumpversion.cfg --- python-django-otp-0.7.5/.bumpversion.cfg 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/.bumpversion.cfg 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.7.5 +current_version = 0.8.1 commit = true message = Version {new_version} tag = true diff -Nru python-django-otp-0.7.5/CHANGES.rst python-django-otp-0.8.1/CHANGES.rst --- python-django-otp-0.7.5/CHANGES.rst 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/CHANGES.rst 2020-02-08 16:26:28.000000000 +0000 @@ -1,3 +1,25 @@ +v0.8.1 - February 08, 2020 - Admin fix +-------------------------------------------------------------------------------- + +- `#26`_: Display OTP Token field on the login page even when user has not yet + authenticated. + + +v0.8.0 - February 06, 2020 - Drop Python 2 support +-------------------------------------------------------------------------------- + +- `#17`_: Drop Python 2 support. + +- `#18`_: Back to a single login template for now. + +- `#23`_: Allow :setting:`OTP_HOTP_ISSUER` and :setting:`OTP_TOTP_ISSUER` to be + callable. + +.. _#17: https://github.com/django-otp/django-otp/pulls/17 +.. _#18: https://github.com/django-otp/django-otp/pulls/18 +.. _#23: https://github.com/django-otp/django-otp/pulls/23 + + v0.7.5 - December 27, 2019 - Django 3.0 support -------------------------------------------------------------------------------- @@ -60,9 +82,7 @@ v0.5.2 - February 11 - 2019 - Fix URL encoding ---------------------------------------------- -- `PR 39`_: Fix encoding of otpauth:// URL parameters. - -.. _PR 39: https://bitbucket.org/psagers/django-otp/pull-requests/39 +- Fix encoding of otpauth:// URL parameters. v0.5.1 - October 24, 2018 - Customizable error messages @@ -98,9 +118,7 @@ - Improved handling of device persistent identifiers. -- Fix `#25`_: make sure default keys are unicode values. - -.. _#25: https://bitbucket.org/psagers/django-otp/issues/25/attributeerror-bytes-object-has-no +- Make sure default keys are unicode values. v0.4.0 - July 19, 2017 - Update support matrix @@ -132,14 +150,12 @@ v0.3.11 - March 8, 2017 - Built-in QR Code support -------------------------------------------------- -- `#20`_: Generate HOTP and TOTP otpauth URLs and corresponding QR Codes. To - enable this feature, install ``django-otp[qrcode]`` or just install the - `qrcode`_ package. +- Generate HOTP and TOTP otpauth URLs and corresponding QR Codes. To enable this + feature, install ``django-otp[qrcode]`` or just install the `qrcode`_ package. - Support for Python 2.6 and Django 1.4 were dropped in this version (long overdue). -.. _#20: https://bitbucket.org/psagers/django-otp/issues/20/how-to-pair-from-the-admin .. _qrcode: https://pypi.python.org/pypi/qrcode/ @@ -163,15 +179,11 @@ v0.3.6 - September 4, 2016 - Django 1.10 ---------------------------------------- -- `#11`_: Don't break the laziness of ``request.user``. - -- `#16`_: Improved error message for invalid tokens. +- Don't break the laziness of ``request.user``. -- `#17`_: Support the new middleware API in Django 1.10. +- Improved error message for invalid tokens. -.. _#11: https://bitbucket.org/psagers/django-otp/issues/11/wasteful-queries-on-every-request -.. _#16: https://bitbucket.org/psagers/django-otp/issues/16/inappropriate-error-when-_verify_token -.. _#17: https://bitbucket.org/psagers/django-otp/issues/17/django-110-new-style-middleware +- Support the new middleware API in Django 1.10. v0.3.5 - April 13, 2016 - Fix default TOTP key diff -Nru python-django-otp-0.7.5/debian/changelog python-django-otp-0.8.1/debian/changelog --- python-django-otp-0.7.5/debian/changelog 2020-01-04 19:58:06.000000000 +0000 +++ python-django-otp-0.8.1/debian/changelog 2020-02-24 10:00:46.000000000 +0000 @@ -1,3 +1,11 @@ +python-django-otp (0.8.1-1) unstable; urgency=low + + * New upstream release. + * Refresh patches. + * Bump Standards-Version to 4.5.0. + + -- Michael Fladischer Mon, 24 Feb 2020 11:00:46 +0100 + python-django-otp (0.7.5-1) unstable; urgency=low [ Ondřej Nový ] diff -Nru python-django-otp-0.7.5/debian/control python-django-otp-0.8.1/debian/control --- python-django-otp-0.7.5/debian/control 2020-01-04 19:58:06.000000000 +0000 +++ python-django-otp-0.8.1/debian/control 2020-02-24 10:00:46.000000000 +0000 @@ -12,7 +12,7 @@ python3-freezegun, python3-setuptools, python3-sphinx, -Standards-Version: 4.4.1 +Standards-Version: 4.5.0 Homepage: https://bitbucket.org/psagers/django-otp Vcs-Browser: https://salsa.debian.org/python-team/modules/python-django-otp Vcs-Git: https://salsa.debian.org/python-team/modules/python-django-otp.git diff -Nru python-django-otp-0.7.5/debian/patches/0001-Use-local-objects.inv-file-for-intersphinx.patch python-django-otp-0.8.1/debian/patches/0001-Use-local-objects.inv-file-for-intersphinx.patch --- python-django-otp-0.7.5/debian/patches/0001-Use-local-objects.inv-file-for-intersphinx.patch 2020-01-04 19:58:06.000000000 +0000 +++ python-django-otp-0.8.1/debian/patches/0001-Use-local-objects.inv-file-for-intersphinx.patch 2020-02-24 10:00:46.000000000 +0000 @@ -8,10 +8,10 @@ 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py -index 5f0ec45..d5055bf 100644 +index ec5ef12..05bb04b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py -@@ -62,11 +62,29 @@ django.conf.settings.configure( +@@ -60,11 +60,29 @@ django.conf.settings.configure( ) django.setup() diff -Nru python-django-otp-0.7.5/debian/patches/0002-Call-django.setup-to-initialize-app-registry.patch python-django-otp-0.8.1/debian/patches/0002-Call-django.setup-to-initialize-app-registry.patch --- python-django-otp-0.7.5/debian/patches/0002-Call-django.setup-to-initialize-app-registry.patch 2020-01-04 19:58:06.000000000 +0000 +++ python-django-otp-0.8.1/debian/patches/0002-Call-django.setup-to-initialize-app-registry.patch 2020-02-24 10:00:46.000000000 +0000 @@ -7,10 +7,10 @@ 1 file changed, 2 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py -index d5055bf..ea3f174 100644 +index 05bb04b..eb2d6bb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py -@@ -63,6 +63,8 @@ django.conf.settings.configure( +@@ -61,6 +61,8 @@ django.conf.settings.configure( django.setup() diff -Nru python-django-otp-0.7.5/debian/patches/0003-Remove-remote-image-shields-to-prevent-privacy-issue.patch python-django-otp-0.8.1/debian/patches/0003-Remove-remote-image-shields-to-prevent-privacy-issue.patch --- python-django-otp-0.7.5/debian/patches/0003-Remove-remote-image-shields-to-prevent-privacy-issue.patch 2020-01-04 19:58:06.000000000 +0000 +++ python-django-otp-0.8.1/debian/patches/0003-Remove-remote-image-shields-to-prevent-privacy-issue.patch 2020-02-24 10:00:46.000000000 +0000 @@ -7,10 +7,13 @@ 1 file changed, 10 deletions(-) diff --git a/README.rst b/README.rst -index e312d6e..50a98b5 100644 +index f868801..b8ccaa8 100644 --- a/README.rst +++ b/README.rst -@@ -1,13 +1,3 @@ +@@ -1,16 +1,6 @@ + django-otp + ========== + -.. image:: https://img.shields.io/pypi/v/django-otp?color=blue - :target: https://pypi.org/project/django-otp/ - :alt: PyPI diff -Nru python-django-otp-0.7.5/docs/source/conf.py python-django-otp-0.8.1/docs/source/conf.py --- python-django-otp-0.7.5/docs/source/conf.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/docs/source/conf.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # django-otp documentation build configuration file, created by # sphinx-quickstart on Fri Jul 13 09:48:33 2012. # @@ -81,15 +79,15 @@ master_doc = 'index' # General information about the project. -project = u'django-otp' -copyright = u'2012, Peter Sagerson' +project = 'django-otp' +copyright = '2012, Peter Sagerson' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The full version, including alpha/beta/rc tags. -release = '0.7.5' +release = '0.8.1' # The short X.Y version. version = '.'.join(release.split('.')[:2]) @@ -225,7 +223,7 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'django-otp.tex', u'django-otp Documentation', + ('index', 'django-otp.tex', 'django-otp Documentation', 'Peter Sagerson', 'manual'), ] @@ -255,7 +253,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'django-otp', u'django-otp Documentation', + ('index', 'django-otp', 'django-otp Documentation', ['Peter Sagerson'], 1) ] @@ -269,7 +267,7 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'django-otp', u'django-otp Documentation', + ('index', 'django-otp', 'django-otp Documentation', 'Peter Sagerson', 'django-otp', 'One line description of project.', 'Miscellaneous'), ] diff -Nru python-django-otp-0.7.5/docs/source/extend.rst python-django-otp-0.8.1/docs/source/extend.rst --- python-django-otp-0.7.5/docs/source/extend.rst 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/docs/source/extend.rst 2020-02-08 16:26:28.000000000 +0000 @@ -31,7 +31,7 @@ key = models.CharField(max_length=80, validators=[hex_validator()], default=lambda: random_hex(20), - help_text=u'A hex-encoded secret key of up to 40 bytes.') + help_text='A hex-encoded secret key of up to 40 bytes.') @property def bin_key(self): diff -Nru python-django-otp-0.7.5/docs/source/index.rst python-django-otp-0.8.1/docs/source/index.rst --- python-django-otp-0.7.5/docs/source/index.rst 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/docs/source/index.rst 2020-02-08 16:26:28.000000000 +0000 @@ -1,7 +1,8 @@ -django-otp -========== - .. include:: ../../README.rst + :end-before: end-of-doc-include + +Contents +-------- .. toctree:: :maxdepth: 3 diff -Nru python-django-otp-0.7.5/docs/source/overview.rst python-django-otp-0.8.1/docs/source/overview.rst --- python-django-otp-0.7.5/docs/source/overview.rst 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/docs/source/overview.rst 2020-02-08 16:26:28.000000000 +0000 @@ -31,8 +31,7 @@ usual way. #. Add :class:`django_otp.middleware.OTPMiddleware` to - :setting:`MIDDLEWARE` or :setting:`MIDDLEWARE_CLASSES`. It must be - installed *after* + :setting:`MIDDLEWARE`. It must be installed *after* :class:`~django.contrib.auth.middleware.AuthenticationMiddleware`. For example:: @@ -261,6 +260,7 @@ The ``issuer`` parameter for the otpauth URL generated by :attr:`~django_otp.plugins.otp_hotp.models.HOTPDevice.config_url`. +This can be a string or a callable to dynamically set the value. .. setting:: OTP_HOTP_THROTTLE_FACTOR @@ -313,7 +313,7 @@ The ``issuer`` parameter for the otpauth URL generated by :attr:`~django_otp.plugins.otp_totp.models.TOTPDevice.config_url`. - +This can be a string or a callable to dynamically set the value. .. setting:: OTP_TOTP_SYNC diff -Nru python-django-otp-0.7.5/.isort.cfg python-django-otp-0.8.1/.isort.cfg --- python-django-otp-0.7.5/.isort.cfg 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/.isort.cfg 2020-02-08 16:26:28.000000000 +0000 @@ -4,8 +4,7 @@ lines_after_imports = 2 multi_line_output = 5 -sections = FUTURE,STDLIB,SIX,THIRDPARTY,DJANGO,FIRSTPARTY,LOCALFOLDER -known_six = six +sections = FUTURE,STDLIB,THIRDPARTY,DJANGO,FIRSTPARTY,LOCALFOLDER known_third_party = freezegun known_django = django known_first_party = django_otp diff -Nru python-django-otp-0.7.5/Pipfile.lock python-django-otp-0.8.1/Pipfile.lock --- python-django-otp-0.7.5/Pipfile.lock 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/Pipfile.lock 2020-02-08 16:26:28.000000000 +0000 @@ -24,12 +24,19 @@ ], "version": "==0.7.12" }, + "asgiref": { + "hashes": [ + "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0", + "sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5" + ], + "version": "==3.2.3" + }, "babel": { "hashes": [ - "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", - "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28" + "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", + "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4" ], - "version": "==2.7.0" + "version": "==2.8.0" }, "bleach": { "hashes": [ @@ -48,10 +55,10 @@ }, "certifi": { "hashes": [ - "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", - "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" + "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", + "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" ], - "version": "==2019.9.11" + "version": "==2019.11.28" }, "chardet": { "hashes": [ @@ -62,49 +69,48 @@ }, "coverage": { "hashes": [ - "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", - "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", - "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", - "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", - "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", - "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", - "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", - "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", - "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", - "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", - "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", - "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", - "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", - "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", - "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", - "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", - "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", - "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", - "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", - "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", - "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", - "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", - "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", - "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", - "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", - "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", - "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", - "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", - "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", - "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", - "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", - "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025" + "sha256:0101888bd1592a20ccadae081ba10e8b204d20235d18d05c6f7d5e904a38fc10", + "sha256:04b961862334687549eb91cd5178a6fbe977ad365bddc7c60f2227f2f9880cf4", + "sha256:1ca43dbd739c0fc30b0a3637a003a0d2c7edc1dd618359d58cc1e211742f8bd1", + "sha256:1cbb88b34187bdb841f2599770b7e6ff8e259dc3bb64fc7893acf44998acf5f8", + "sha256:232f0b52a5b978288f0bbc282a6c03fe48cd19a04202df44309919c142b3bb9c", + "sha256:24bcfa86fd9ce86b73a8368383c39d919c497a06eebb888b6f0c12f13e920b1a", + "sha256:25b8f60b5c7da71e64c18888f3067d5b6f1334b9681876b2fb41eea26de881ae", + "sha256:2714160a63da18aed9340c70ed514973971ee7e665e6b336917ff4cca81a25b1", + "sha256:2ca2cd5264e84b2cafc73f0045437f70c6378c0d7dbcddc9ee3fe192c1e29e5d", + "sha256:2cc707fc9aad2592fc686d63ef72dc0031fc98b6fb921d2f5395d9ab84fbc3ef", + "sha256:348630edea485f4228233c2f310a598abf8afa5f8c716c02a9698089687b6085", + "sha256:40fbfd6b044c9db13aeec1daf5887d322c710d811f944011757526ef6e323fd9", + "sha256:46c9c6a1d1190c0b75ec7c0f339088309952b82ae8d67a79ff1319eb4e749b96", + "sha256:591506e088901bdc25620c37aec885e82cc896528f28c57e113751e3471fc314", + "sha256:5ac71bba1e07eab403b082c4428f868c1c9e26a21041436b4905c4c3d4e49b08", + "sha256:5f622f19abda4e934938e24f1d67599249abc201844933a6f01aaa8663094489", + "sha256:65bead1ac8c8930cf92a1ccaedcce19a57298547d5d1db5c9d4d068a0675c38b", + "sha256:7362a7f829feda10c7265b553455de596b83d1623b3d436b6d3c51c688c57bf6", + "sha256:7f2675750c50151f806070ec11258edf4c328340916c53bac0adbc465abd6b1e", + "sha256:960d7f42277391e8b1c0b0ae427a214e1b31a1278de6b73f8807b20c2e913bba", + "sha256:a50b0888d8a021a3342d36a6086501e30de7d840ab68fca44913e97d14487dc1", + "sha256:b7dbc5e8c39ea3ad3db22715f1b5401cd698a621218680c6daf42c2f9d36e205", + "sha256:bb3d29df5d07d5399d58a394d0ef50adf303ab4fbf66dfd25b9ef258effcb692", + "sha256:c0fff2733f7c2950f58a4fd09b5db257b00c6fec57bf3f68c5bae004d804b407", + "sha256:c792d3707a86c01c02607ae74364854220fb3e82735f631cd0a345dea6b4cee5", + "sha256:c90bda74e16bcd03861b09b1d37c0a4158feda5d5a036bb2d6e58de6ff65793e", + "sha256:cfce79ce41cc1a1dc7fc85bb41eeeb32d34a4cf39a645c717c0550287e30ff06", + "sha256:eeafb646f374988c22c8e6da5ab9fb81367ecfe81c70c292623373d2a021b1a1", + "sha256:f425f50a6dd807cb9043d15a4fcfba3b5874a54d9587ccbb748899f70dc18c47", + "sha256:fcd4459fe35a400b8f416bc57906862693c9f88b66dc925e7f2a933e77f6b18b", + "sha256:ff3936dd5feaefb4f91c8c1f50a06c588b5dc69fba4f7d9c79a6617ad80bb7df" ], "index": "pypi", - "version": "==4.5.4" + "version": "==5.0.1" }, "django": { "hashes": [ - "sha256:16040e1288c6c9f68c6da2fe75ebde83c0a158f6f5d54f4c5177b0c1478c5b86", - "sha256:89c2007ca4fa5b351a51a279eccff298520783b713bf28efb89dfb81c80ea49b" + "sha256:4f2c913303be4f874015993420bf0bd8fd2097a9c88e6b49c6a92f9bdd3fb13a", + "sha256:8c3575f81e11390893860d97e1e0154c47512f180ea55bd84ce8fa69ba8051ca" ], "index": "pypi", - "version": "==2.2.7" + "version": "==3.0.2" }, "django-otp": { "editable": true, @@ -157,18 +163,18 @@ }, "imagesize": { "hashes": [ - "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", - "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" + "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", + "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1" ], - "version": "==1.1.0" + "version": "==1.2.0" }, "importlib-metadata": { "hashes": [ - "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", - "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" + "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45", + "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f" ], "markers": "python_version < '3.8'", - "version": "==0.23" + "version": "==1.3.0" }, "isort": { "hashes": [ @@ -187,10 +193,10 @@ }, "keyring": { "hashes": [ - "sha256:91037ccaf0c9a112a76f7740e4a416b9457a69b66c2799421581bee710a974b3", - "sha256:f5bb20ea6c57c2360daf0c591931c9ea0d7660a8d9e32ca84d63273f131ea605" + "sha256:5f5f92327b6c7432bebc18a1b60cb3797d99b08db1f5b919b8187c37a01f1ccc", + "sha256:ad84f7fe26ab51731f089eaf1c44ebf4c5fae323653c908888a3a6212ad0bbe7" ], - "version": "==19.2.0" + "version": "==21.0.0" }, "markupsafe": { "hashes": [ @@ -234,10 +240,10 @@ }, "more-itertools": { "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d", + "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564" ], - "version": "==7.2.0" + "version": "==8.0.2" }, "packaging": { "hashes": [ @@ -262,10 +268,10 @@ }, "py": { "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", + "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" ], - "version": "==1.8.0" + "version": "==1.8.1" }, "pycodestyle": { "hashes": [ @@ -283,17 +289,17 @@ }, "pygments": { "hashes": [ - "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", - "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" + "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", + "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" ], - "version": "==2.4.2" + "version": "==2.5.2" }, "pyparsing": { "hashes": [ - "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", - "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" + "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", + "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" ], - "version": "==2.4.5" + "version": "==2.4.6" }, "python-dateutil": { "hashes": [ @@ -346,11 +352,11 @@ }, "sphinx": { "hashes": [ - "sha256:31088dfb95359384b1005619827eaee3056243798c62724fd3fa4b84ee4d71bd", - "sha256:52286a0b9d7caa31efee301ec4300dbdab23c3b05da1c9024b4e84896fb73d79" + "sha256:298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159", + "sha256:e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5" ], "index": "pypi", - "version": "==2.2.1" + "version": "==2.3.1" }, "sphinxcontrib-applehelp": { "hashes": [ @@ -410,26 +416,26 @@ }, "tox": { "hashes": [ - "sha256:1d1368ac86e8332f79e2bcef9fefe2b077469f08449eadf0183759b34f3b2070", - "sha256:bcfa3e40abc1e9b70607b56adfd976fe7dc8286ad56aab44e3151daca7d2d0d0" + "sha256:06ba73b149bf838d5cd25dc30c2dd2671ae5b2757cf98e5c41a35fe449f131b3", + "sha256:806d0a9217584558cc93747a945a9d9bff10b141a5287f0c8429a08828a22192" ], "index": "pypi", - "version": "==3.14.1" + "version": "==3.14.3" }, "tqdm": { "hashes": [ - "sha256:9de4722323451eb7818deb0161d9d5523465353a6707a9f500d97ee42919b902", - "sha256:c1d677f3a85fa291b34bdf8f770f877119b9754b32673699653556f85e2c2f13" + "sha256:4789ccbb6fc122b5a6a85d512e4e41fc5acad77216533a6f2b8ce51e0f265c23", + "sha256:efab950cf7cc1e4d8ee50b2bb9c8e4a89f8307b49e0b2c9cfef3ec4ca26655eb" ], - "version": "==4.38.0" + "version": "==4.41.1" }, "twine": { "hashes": [ - "sha256:8d85e75338c97ea7ed04330b1dce1d948ce83cec333fb9a0e26a11ffdc4a40dd", - "sha256:af3a83c627bd609d3ffe0d48f420e28584c448764ceeb203bb8eafdc8eabb250" + "sha256:c1af8ca391e43b0a06bbc155f7f67db0bf0d19d284bfc88d1675da497a946124", + "sha256:d561a5e511f70275e5a485a6275ff61851c16ffcb3a95a602189161112d9f160" ], "index": "pypi", - "version": "==3.0.0" + "version": "==3.1.1" }, "urllib3": { "hashes": [ @@ -440,10 +446,10 @@ }, "virtualenv": { "hashes": [ - "sha256:11cb4608930d5fd3afb545ecf8db83fa50e1f96fc4fca80c94b07d2c83146589", - "sha256:d257bb3773e48cac60e475a19b608996c73f4d333b3ba2e4e57d5ac6134e0136" + "sha256:0d62c70883c0342d59c11d0ddac0d954d0431321a41ab20851facf2b222598f3", + "sha256:55059a7a676e4e19498f1aad09b8313a38fcc0cdbe4fdddc0e9b06946d21b4bb" ], - "version": "==16.7.7" + "version": "==16.7.9" }, "webencodings": { "hashes": [ diff -Nru python-django-otp-0.7.5/README.rst python-django-otp-0.8.1/README.rst --- python-django-otp-0.7.5/README.rst 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/README.rst 2020-02-08 16:26:28.000000000 +0000 @@ -1,3 +1,6 @@ +django-otp +========== + .. image:: https://img.shields.io/pypi/v/django-otp?color=blue :target: https://pypi.org/project/django-otp/ :alt: PyPI @@ -16,10 +19,36 @@ OTPs into their Django projects as a form of `two-factor authentication `_. -This project includes several simple OTP plugins and more are available -separately. This package also includes an implementation of OATH `HOTP +Several simple OTP plugins are included and more are available separately. This +package also includes an implementation of OATH `HOTP `_ and `TOTP `_ for convenience, as these are standard OTP algorithms used by multiple plugins. -.. _upgrade notes: https://django-otp-official.readthedocs.io/#upgrading +If you're looking for a higher-level or more opinionated solution, you might be +interested in `django-two-factor-auth +`_. + +Status +------ + +This project is stable and maintained, but is no longer actively used by the +author. Well-formed pull requests are welcome. Anyone interested in taking over +aspects of the project should `contact me `_. + +.. end-of-doc-include + +Development +----------- + +Development dependencies are defined in the Pipfile; use `pipenv`_ to set up a +suitable shell. + +The tests in tox.ini cover a representative sample of supported Python and +Django versions, as well as running `flake8`_ and `isort`_ for linting and style +consistency. Please run `tox` before checking in and sending a pull request. + + +.. _pipenv: https://pipenv.readthedocs.io/en/latest/ +.. _flake8: https://pypi.org/project/flake8/ +.. _isort: https://pypi.org/project/isort/ diff -Nru python-django-otp-0.7.5/setup.cfg python-django-otp-0.8.1/setup.cfg --- python-django-otp-0.7.5/setup.cfg 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/setup.cfg 2020-02-08 16:26:28.000000000 +0000 @@ -7,6 +7,3 @@ W504 # line too long E501 - -[bdist_wheel] -universal = 1 diff -Nru python-django-otp-0.7.5/setup.py python-django-otp-0.8.1/setup.py --- python-django-otp-0.7.5/setup.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/setup.py 2020-02-08 16:26:28.000000000 +0000 @@ -39,7 +39,7 @@ setup( name='django-otp', - version='0.7.5', + version='0.8.1', description="A pluggable framework for adding two-factor authentication to Django using one-time passwords.", license='BSD', author="Peter Sagerson", @@ -54,8 +54,8 @@ "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", - "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", "Topic :: Security", "Topic :: Software Development :: Libraries :: Python Modules", ], @@ -72,7 +72,6 @@ install_requires=[ 'django >= 1.11', - 'six >= 1.10.0' ], extras_require={ 'qrcode': ['qrcode'], diff -Nru python-django-otp-0.7.5/src/django_otp/admin.py python-django-otp-0.8.1/src/django_otp/admin.py --- python-django-otp-0.7.5/src/django_otp/admin.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/admin.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,6 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - -import django from django import forms from django.contrib.admin.forms import AdminAuthenticationForm from django.contrib.admin.sites import AdminSite @@ -13,13 +10,10 @@ Returns the most appropriate Django login template available. In the past, we've had more version-specific templates. Perhaps this will - be true again in the future. For now, the Django 1.9 and 3.0 version are - the most recent available. + be true again in the future. For now, the Django 1.11 version is suitable + even with the most recent Django version. """ - if django.VERSION[0] >= 3: - return 'otp/admin30/login.html' - else: - return 'otp/admin19/login.html' + return 'otp/admin111/login.html' class OTPAdminAuthenticationForm(AdminAuthenticationForm, OTPAuthenticationFormMixin): @@ -35,17 +29,8 @@ # the otp_challenge submit button. otp_challenge = forms.CharField(required=False) - def __init__(self, *args, **kwargs): - super(OTPAdminAuthenticationForm, self).__init__(*args, **kwargs) - - # A litle extra cheese to make it prettier. - minor_django_version = django.VERSION[:2] - - if minor_django_version < (1, 6): - self.fields['otp_token'].widget.attrs['style'] = 'width: 14em;' - def clean(self): - self.cleaned_data = super(OTPAdminAuthenticationForm, self).clean() + self.cleaned_data = super().clean() self.clean_otp(self.get_user()) return self.cleaned_data @@ -72,11 +57,11 @@ login_template = _admin_template_for_django_version() def __init__(self, name='otpadmin'): - super(OTPAdminSite, self).__init__(name) + super().__init__(name) def has_permission(self, request): """ In addition to the default requirements, this only allows access to users who have been verified by a registered OTP device. """ - return super(OTPAdminSite, self).has_permission(request) and request.user.is_verified() + return super().has_permission(request) and request.user.is_verified() diff -Nru python-django-otp-0.7.5/src/django_otp/conf.py python-django-otp-0.8.1/src/django_otp/conf.py --- python-django-otp-0.7.5/src/django_otp/conf.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/conf.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,11 +1,7 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - -from six import iteritems - import django.conf -class Settings(object): +class Settings: """ This is a simple class to take the place of the global settings object. An instance will contain all of our settings as attributes, with default values @@ -20,7 +16,7 @@ Loads our settings from django.conf.settings, applying defaults for any that are omitted. """ - for name, default in iteritems(self.defaults): + for name, default in self.defaults.items(): value = getattr(django.conf.settings, name, default) setattr(self, name, value) diff -Nru python-django-otp-0.7.5/src/django_otp/decorators.py python-django-otp-0.8.1/src/django_otp/decorators.py --- python-django-otp-0.7.5/src/django_otp/decorators.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/decorators.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from django.contrib.auth.decorators import user_passes_test from django_otp import user_has_device diff -Nru python-django-otp-0.7.5/src/django_otp/forms.py python-django-otp-0.8.1/src/django_otp/forms.py --- python-django-otp-0.7.5/src/django_otp/forms.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/forms.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,15 +1,13 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from django import forms from django.contrib.auth.forms import AuthenticationForm -from django.utils.translation import ugettext_lazy as _ -from django.utils.translation import ungettext_lazy +from django.utils.translation import gettext_lazy as _ +from django.utils.translation import ngettext_lazy from . import devices_for_user, match_token from .models import Device, VerifyNotAllowed -class OTPAuthenticationFormMixin(object): +class OTPAuthenticationFormMixin: """ Shared functionality for :class:`~django.contrib.auth.forms.AuthenticationForm` subclasses that wish @@ -62,7 +60,7 @@ 'not_interactive': _('The selected OTP device is not interactive'), 'challenge_message': _('OTP Challenge: {0}'), 'invalid_token': _('Invalid token. Please make sure you have entered it correctly.'), - 'n_failed_attempts': ungettext_lazy( + 'n_failed_attempts': ngettext_lazy( "Verification temporarily disabled because of %(failure_count)d failed attempt, please try again soon.", "Verification temporarily disabled because of %(failure_count)d failed attempts, please try again soon.", "failure_count"), @@ -235,7 +233,7 @@ otp_challenge = forms.CharField(required=False) def clean(self): - self.cleaned_data = super(OTPAuthenticationForm, self).clean() + self.cleaned_data = super().clean() self.clean_otp(self.get_user()) return self.cleaned_data @@ -282,13 +280,13 @@ otp_challenge = forms.CharField(required=False) def __init__(self, user, request=None, *args, **kwargs): - super(OTPTokenForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.user = user self.fields['otp_device'].choices = self.device_choices(user) def clean(self): - super(OTPTokenForm, self).clean() + super().clean() self.clean_otp(self.user) diff -Nru python-django-otp-0.7.5/src/django_otp/middleware.py python-django-otp-0.8.1/src/django_otp/middleware.py --- python-django-otp-0.7.5/src/django_otp/middleware.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/middleware.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - import functools from django.utils.functional import SimpleLazyObject @@ -12,7 +10,7 @@ return user.otp_device is not None -class OTPMiddleware(object): +class OTPMiddleware: """ This must be installed after :class:`~django.contrib.auth.middleware.AuthenticationMiddleware` and diff -Nru python-django-otp-0.7.5/src/django_otp/models.py python-django-otp-0.8.1/src/django_otp/models.py --- python-django-otp-0.7.5/src/django_otp/models.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/models.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,7 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - -import six - from django.apps import apps from django.conf import settings from django.core.exceptions import ObjectDoesNotExist @@ -77,16 +73,10 @@ objects = DeviceManager() - class Meta(object): + class Meta: abstract = True def __str__(self): - if six.PY3: - return self.__unicode__() - else: - return self.__unicode__().encode('utf-8') - - def __unicode__(self): try: user = self.user except ObjectDoesNotExist: @@ -256,7 +246,7 @@ 'failure_count': self.throttling_failure_count, }) - return super(ThrottlingMixin, self).verify_is_allowed() + return super().verify_is_allowed() def throttle_reset(self, commit=True): """ diff -Nru python-django-otp-0.7.5/src/django_otp/oath.py python-django-otp-0.8.1/src/django_otp/oath.py --- python-django-otp-0.7.5/src/django_otp/oath.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/oath.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,19 +1,8 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from hashlib import sha1 import hmac from struct import pack from time import time -import six - - -if six.PY3: - iterbytes = iter -else: - def iterbytes(buf): - return (ord(b) for b in buf) - def hotp(key, counter, digits=6): """ @@ -43,7 +32,7 @@ """ msg = pack(b'>Q', counter) hs = hmac.new(key, msg, sha1).digest() - hs = list(iterbytes(hs)) + hs = list(iter(hs)) offset = hs[19] & 0x0f bin_code = (hs[offset] & 0x7f) << 24 | hs[offset + 1] << 16 | hs[offset + 2] << 8 | hs[offset + 3] @@ -87,7 +76,7 @@ return TOTP(key, step, t0, digits, drift).token() -class TOTP(object): +class TOTP: """ An alternate TOTP interface. diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_email/admin.py python-django-otp-0.8.1/src/django_otp/plugins/otp_email/admin.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_email/admin.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_email/admin.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from django.contrib import admin from django.contrib.admin.sites import AlreadyRegistered diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_email/conf.py python-django-otp-0.8.1/src/django_otp/plugins/otp_email/conf.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_email/conf.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_email/conf.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,11 +1,7 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - -from six import iteritems - import django.conf -class OTPEmailSettings(object): +class OTPEmailSettings: """ This is a simple class to take the place of the global settings object. An instance will contain all of our settings as attributes, with default values @@ -21,7 +17,7 @@ Loads our settings from django.conf.settings, applying defaults for any that are omitted. """ - for name, default in iteritems(self.defaults): + for name, default in self.defaults.items(): value = getattr(django.conf.settings, name, default) setattr(self, name, value) diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_email/migrations/0001_initial.py python-django-otp-0.8.1/src/django_otp/plugins/otp_email/migrations/0001_initial.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_email/migrations/0001_initial.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_email/migrations/0001_initial.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_email/models.py python-django-otp-0.8.1/src/django_otp/plugins/otp_email/models.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_email/models.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_email/models.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,11 +1,8 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from binascii import unhexlify from django.core.mail import send_mail from django.db import models from django.template.loader import render_to_string -from django.utils.encoding import force_text from django_otp.models import Device from django_otp.oath import totp @@ -15,7 +12,7 @@ def default_key(): - return force_text(random_hex(20)) + return random_hex(20) def key_validator(value): diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_email/tests.py python-django-otp-0.8.1/src/django_otp/plugins/otp_email/tests.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_email/tests.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_email/tests.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from django.core import mail from django.db import IntegrityError from django.test.utils import override_settings diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_hotp/admin.py python-django-otp-0.8.1/src/django_otp/plugins/otp_hotp/admin.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_hotp/admin.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_hotp/admin.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from django.conf.urls import url from django.contrib import admin from django.contrib.admin.sites import AlreadyRegistered @@ -40,7 +38,7 @@ radio_fields = {'digits': admin.HORIZONTAL} def get_queryset(self, request): - queryset = super(HOTPDeviceAdmin, self).get_queryset(request) + queryset = super().get_queryset(request) queryset = queryset.select_related('user') return queryset @@ -67,7 +65,7 @@ urls = [ url(r'^(?P\d+)/config/$', self.admin_site.admin_view(self.config_view), name='otp_hotp_hotpdevice_config'), url(r'^(?P\d+)/qrcode/$', self.admin_site.admin_view(self.qrcode_view), name='otp_hotp_hotpdevice_qrcode'), - ] + super(HOTPDeviceAdmin, self).get_urls() + ] + super().get_urls() return urls diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_hotp/migrations/0001_initial.py python-django-otp-0.8.1/src/django_otp/plugins/otp_hotp/migrations/0001_initial.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_hotp/migrations/0001_initial.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_hotp/migrations/0001_initial.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_hotp/models.py python-django-otp-0.8.1/src/django_otp/plugins/otp_hotp/models.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_hotp/models.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_hotp/models.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,14 +1,9 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from base64 import b32encode from binascii import unhexlify - -from six import string_types -from six.moves.urllib.parse import quote, urlencode +from urllib.parse import quote, urlencode from django.conf import settings from django.db import models -from django.utils.encoding import force_text from django_otp.models import Device, ThrottlingMixin from django_otp.oath import hotp @@ -16,7 +11,7 @@ def default_key(): - return force_text(random_hex(20)) + return random_hex(20) def key_validator(value): @@ -113,7 +108,9 @@ urlencoded_params = urlencode(params) issuer = getattr(settings, 'OTP_HOTP_ISSUER', None) - if isinstance(issuer, string_types) and (issuer != ''): + if callable(issuer): + issuer = issuer(self) + if isinstance(issuer, str) and (issuer != ''): issuer = issuer.replace(':', '') label = '{}:{}'.format(issuer, label) urlencoded_params += '&issuer={}'.format(quote(issuer)) # encode issuer as per RFC 3986, not quote_plus diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_hotp/tests.py python-django-otp-0.8.1/src/django_otp/plugins/otp_hotp/tests.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_hotp/tests.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_hotp/tests.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,8 +1,5 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from datetime import timedelta - -from six.moves.urllib.parse import parse_qs, urlsplit +from urllib.parse import parse_qs, urlsplit from freezegun import freeze_time @@ -21,7 +18,8 @@ def setUp(self): try: - alice = self.create_user('alice', 'password') + alice = self.create_user( + 'alice', 'password', email='alice@example.com') except IntegrityError: self.skipTest("Unable to create test user.") else: @@ -140,6 +138,20 @@ self.assertIn('issuer', params) self.assertEqual(params['issuer'][0], 'Very Trustworthy Source') + def test_config_url_issuer_method(self): + with override_settings(OTP_HOTP_ISSUER=lambda d: d.user.email): + url = self.device.config_url + + parsed = urlsplit(url) + params = parse_qs(parsed.query) + + self.assertEqual(parsed.scheme, 'otpauth') + self.assertEqual(parsed.netloc, 'hotp') + self.assertEqual(parsed.path, '/alice%40example.com%3Aalice') + self.assertIn('secret', params) + self.assertIn('issuer', params) + self.assertEqual(params['issuer'][0], 'alice@example.com') + class AuthFormTest(TestCase): """ diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_static/admin.py python-django-otp-0.8.1/src/django_otp/plugins/otp_static/admin.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_static/admin.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_static/admin.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from django.contrib import admin from django.contrib.admin.sites import AlreadyRegistered diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_static/lib.py python-django-otp-0.8.1/src/django_otp/plugins/otp_static/lib.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_static/lib.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_static/lib.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from django.contrib.auth import get_user_model from .models import StaticDevice, StaticToken diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_static/management/commands/addstatictoken.py python-django-otp-0.8.1/src/django_otp/plugins/otp_static/management/commands/addstatictoken.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_static/management/commands/addstatictoken.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_static/management/commands/addstatictoken.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,9 +1,7 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from textwrap import fill from django.core.management.base import BaseCommand, CommandError -from django.utils.encoding import force_text +from django.utils.encoding import force_str from django_otp.plugins.otp_static.lib import add_static_token, get_user_model @@ -25,4 +23,4 @@ except get_user_model().DoesNotExist: raise CommandError('User "{0}" does not exist.'.format(username)) - self.stdout.write(force_text(statictoken.token)) + self.stdout.write(force_str(statictoken.token)) diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_static/migrations/0001_initial.py python-django-otp-0.8.1/src/django_otp/plugins/otp_static/migrations/0001_initial.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_static/migrations/0001_initial.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_static/migrations/0001_initial.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_static/models.py python-django-otp-0.8.1/src/django_otp/plugins/otp_static/models.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_static/models.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_static/models.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from base64 import b32encode from os import urandom diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_static/tests.py python-django-otp-0.8.1/src/django_otp/plugins/otp_static/tests.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_static/tests.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_static/tests.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from django.db import IntegrityError from django_otp.forms import OTPAuthenticationForm diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_totp/admin.py python-django-otp-0.8.1/src/django_otp/plugins/otp_totp/admin.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_totp/admin.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_totp/admin.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from django.conf.urls import url from django.contrib import admin from django.contrib.admin.sites import AlreadyRegistered @@ -40,7 +38,7 @@ radio_fields = {'digits': admin.HORIZONTAL} def get_queryset(self, request): - queryset = super(TOTPDeviceAdmin, self).get_queryset(request) + queryset = super().get_queryset(request) queryset = queryset.select_related('user') return queryset @@ -67,7 +65,7 @@ urls = [ url(r'^(?P\d+)/config/$', self.admin_site.admin_view(self.config_view), name='otp_totp_totpdevice_config'), url(r'^(?P\d+)/qrcode/$', self.admin_site.admin_view(self.qrcode_view), name='otp_totp_totpdevice_qrcode'), - ] + super(TOTPDeviceAdmin, self).get_urls() + ] + super().get_urls() return urls diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_totp/migrations/0001_initial.py python-django-otp-0.8.1/src/django_otp/plugins/otp_totp/migrations/0001_initial.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_totp/migrations/0001_initial.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_totp/migrations/0001_initial.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_totp/models.py python-django-otp-0.8.1/src/django_otp/plugins/otp_totp/models.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_totp/models.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_totp/models.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,15 +1,10 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from base64 import b32encode from binascii import unhexlify import time - -from six import string_types -from six.moves.urllib.parse import quote, urlencode +from urllib.parse import quote, urlencode from django.conf import settings from django.db import models -from django.utils.encoding import force_text from django_otp.models import Device, ThrottlingMixin from django_otp.oath import TOTP @@ -17,7 +12,7 @@ def default_key(): - return force_text(random_hex(20)) + return random_hex(20) def key_validator(value): @@ -141,7 +136,9 @@ urlencoded_params = urlencode(params) issuer = getattr(settings, 'OTP_TOTP_ISSUER', None) - if isinstance(issuer, string_types) and (issuer != ''): + if callable(issuer): + issuer = issuer(self) + if isinstance(issuer, str) and (issuer != ''): issuer = issuer.replace(':', '') label = '{}:{}'.format(issuer, label) urlencoded_params += '&issuer={}'.format(quote(issuer)) # encode issuer as per RFC 3986, not quote_plus diff -Nru python-django-otp-0.7.5/src/django_otp/plugins/otp_totp/tests.py python-django-otp-0.8.1/src/django_otp/plugins/otp_totp/tests.py --- python-django-otp-0.7.5/src/django_otp/plugins/otp_totp/tests.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/plugins/otp_totp/tests.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,9 +1,6 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from datetime import timedelta from time import time - -from six.moves.urllib.parse import parse_qs, urlsplit +from urllib.parse import parse_qs, urlsplit from freezegun import freeze_time @@ -24,7 +21,8 @@ Create a device at the fourth time step. The current token is 154567. """ try: - self.alice = self.create_user('alice', 'password') + self.alice = self.create_user( + 'alice', 'password', email='alice@example.com') except IntegrityError: self.skipTest("Unable to create the test user.") else: @@ -161,3 +159,17 @@ self.assertIn('secret', params) self.assertIn('issuer', params) self.assertEqual(params['issuer'][0], 'Very Trustworthy Source') + + def test_config_url_issuer_method(self): + with override_settings(OTP_TOTP_ISSUER=lambda d: d.user.email): + url = self.device.config_url + + parsed = urlsplit(url) + params = parse_qs(parsed.query) + + self.assertEqual(parsed.scheme, 'otpauth') + self.assertEqual(parsed.netloc, 'totp') + self.assertEqual(parsed.path, '/alice%40example.com%3Aalice') + self.assertIn('secret', params) + self.assertIn('issuer', params) + self.assertEqual(params['issuer'][0], 'alice@example.com') diff -Nru python-django-otp-0.7.5/src/django_otp/templates/otp/admin111/login.html python-django-otp-0.8.1/src/django_otp/templates/otp/admin111/login.html --- python-django-otp-0.7.5/src/django_otp/templates/otp/admin111/login.html 1970-01-01 00:00:00.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/templates/otp/admin111/login.html 2020-02-08 16:26:28.000000000 +0000 @@ -0,0 +1,97 @@ +{% extends "admin/base_site.html" %} +{% load i18n static %} + +{% block extrastyle %} + {{ block.super }} + + {{ form.media }} + + +{% endblock %} + +{% block bodyclass %}{{ block.super }} login{% endblock %} + +{% block usertools %}{% endblock %} + +{% block nav-global %}{% endblock %} + +{% block content_title %}{% endblock %} + +{% block breadcrumbs %}{% endblock %} + +{% block content %} +{% if form.errors and not form.non_field_errors %} +

+{% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %} +

+{% endif %} + +{% if form.non_field_errors %} +{% for error in form.non_field_errors %} +

+ {{ error }} +

+{% endfor %} +{% endif %} + +
+ +{% if user.is_authenticated %} +

+{% blocktrans with username=request.user.username trimmed %} + You are authenticated as {{ username }}, but are not authorized to + access this page. Would you like to login to a different account? +{% endblocktrans %} +

+{% endif %} + +
{% csrf_token %} +
+ {{ form.username.errors }} + {{ form.username.label_tag }} {{ form.username }} +
+
+ {{ form.password.errors }} + {{ form.password.label_tag }} {{ form.password }} + +
+ {% if form.get_user %} +
+ {{ form.otp_device.errors }} + {{ form.otp_device }} +
+ {% endif %} +
+ {{ form.otp_token.errors }} + {{ form.otp_token }} +
+ {% url 'admin_password_reset' as password_reset_url %} + {% if password_reset_url %} + + {% endif %} +
+ + {% if form.get_user %} + + {% endif %} +
+
+ + +
+{% endblock %} diff -Nru python-django-otp-0.7.5/src/django_otp/templates/otp/admin19/login.html python-django-otp-0.8.1/src/django_otp/templates/otp/admin19/login.html --- python-django-otp-0.7.5/src/django_otp/templates/otp/admin19/login.html 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/templates/otp/admin19/login.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,96 +0,0 @@ -{% extends "admin/base_site.html" %} -{% load i18n admin_static %} - -{% block extrastyle %} - {{ block.super }} - - {{ form.media }} - - -{% endblock %} - -{% block bodyclass %}{{ block.super }} login{% endblock %} - -{% block usertools %}{% endblock %} - -{% block nav-global %}{% endblock %} - -{% block content_title %}{% endblock %} - -{% block breadcrumbs %}{% endblock %} - -{% block content %} -{% if form.errors and not form.non_field_errors %} -

-{% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %} -

-{% endif %} - -{% if form.non_field_errors %} -{% for error in form.non_field_errors %} -

- {{ error }} -

-{% endfor %} -{% endif %} - -
- -{% if user.is_authenticated %} -

-{% blocktrans with username=request.user.username trimmed %} - You are authenticated as {{ username }}, but are not authorized to - access this page. Would you like to login to a different account? -{% endblocktrans %} -

-{% endif %} - -
{% csrf_token %} -
- {{ form.username.errors }} - {{ form.username.label_tag }} {{ form.username }} -
-
- {{ form.password.errors }} - {{ form.password.label_tag }} {{ form.password }} - -
- {% if form.get_user %} -
- {{ form.otp_device }} -
- {% endif %} -
- {% if not form.this_is_the_login_form.errors %}{{ form.otp_token.errors }}{% endif %} - {{ form.otp_token }} -
- {% url 'admin_password_reset' as password_reset_url %} - {% if password_reset_url %} - - {% endif %} -
- - {% if form.get_user %} - - {% endif %} -
-
- - -
-{% endblock %} diff -Nru python-django-otp-0.7.5/src/django_otp/templates/otp/admin30/login.html python-django-otp-0.8.1/src/django_otp/templates/otp/admin30/login.html --- python-django-otp-0.7.5/src/django_otp/templates/otp/admin30/login.html 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/templates/otp/admin30/login.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,96 +0,0 @@ -{% extends "admin/base_site.html" %} -{% load i18n static %} - -{% block extrastyle %} - {{ block.super }} - - {{ form.media }} - - -{% endblock %} - -{% block bodyclass %}{{ block.super }} login{% endblock %} - -{% block usertools %}{% endblock %} - -{% block nav-global %}{% endblock %} - -{% block content_title %}{% endblock %} - -{% block breadcrumbs %}{% endblock %} - -{% block content %} -{% if form.errors and not form.non_field_errors %} -

-{% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %} -

-{% endif %} - -{% if form.non_field_errors %} -{% for error in form.non_field_errors %} -

- {{ error }} -

-{% endfor %} -{% endif %} - -
- -{% if user.is_authenticated %} -

-{% blocktrans with username=request.user.username trimmed %} - You are authenticated as {{ username }}, but are not authorized to - access this page. Would you like to login to a different account? -{% endblocktrans %} -

-{% endif %} - -
{% csrf_token %} -
- {{ form.username.errors }} - {{ form.username.label_tag }} {{ form.username }} -
-
- {{ form.password.errors }} - {{ form.password.label_tag }} {{ form.password }} - -
- {% if form.get_user %} -
- {{ form.otp_device }} -
- {% endif %} -
- {% if not form.this_is_the_login_form.errors %}{{ form.otp_token.errors }}{% endif %} - {{ form.otp_token }} -
- {% url 'admin_password_reset' as password_reset_url %} - {% if password_reset_url %} - - {% endif %} -
- - {% if form.get_user %} - - {% endif %} -
-
- - -
-{% endblock %} diff -Nru python-django-otp-0.7.5/src/django_otp/tests.py python-django-otp-0.8.1/src/django_otp/tests.py --- python-django-otp-0.7.5/src/django_otp/tests.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/tests.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,13 +1,12 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from doctest import DocTestSuite import pickle import unittest -import django from django.contrib.auth import get_user_model from django.db import IntegrityError -import django.test +from django.test import RequestFactory +from django.test import TestCase as DjangoTestCase +from django.urls import reverse from django_otp import DEVICE_ID_SESSION_KEY, oath, util from django_otp.middleware import OTPMiddleware @@ -23,30 +22,30 @@ return suite -class TestCase(django.test.TestCase): +class TestCase(DjangoTestCase): """ Utilities for dealing with custom user models. """ @classmethod def setUpClass(cls): - super(TestCase, cls).setUpClass() + super().setUpClass() cls.User = get_user_model() cls.USERNAME_FIELD = cls.User.USERNAME_FIELD - def create_user(self, username, password): + def create_user(self, username, password, **kwargs): """ Try to create a user, honoring the custom user model, if any. This may raise an exception if the user model is too exotic for our purposes. """ - return self.User.objects.create_user(username, password=password) + return self.User.objects.create_user(username, password=password, **kwargs) class OTPMiddlewareTestCase(TestCase): def setUp(self): - self.factory = django.test.RequestFactory() + self.factory = RequestFactory() try: self.alice = self.create_user('alice', 'password') self.bob = self.create_user('bob', 'password') @@ -146,7 +145,7 @@ def setUp(self): try: self.alice = self.create_user('alice', 'password') - self.bob = self.create_user('bob', 'password') + self.bob = self.create_user('bob', 'password', is_staff=True) except IntegrityError: self.skipTest("Unable to create a test user.") else: @@ -154,6 +153,32 @@ device = user.staticdevice_set.create() device.token_set.create(token=user.get_username()) + def test_admin_login_template(self): + response = self.client.get(reverse('admin:login')) + self.assertContains(response, 'Username:') + self.assertContains(response, 'Password:') + self.assertNotContains(response, 'OTP Device:') + self.assertContains(response, 'OTP Token:') + response = self.client.post(reverse('admin:login'), data={ + 'username': self.bob.get_username(), + 'password': 'password', + }) + self.assertContains(response, 'Username:') + self.assertContains(response, 'Password:') + self.assertContains(response, 'OTP Device:') + self.assertContains(response, 'OTP Token:') + + device = self.bob.staticdevice_set.get() + token = device.token_set.get() + response = self.client.post(reverse('admin:login'), data={ + 'username': self.bob.get_username(), + 'password': 'password', + 'otp_device': device.persistent_id, + 'otp_token': token.token, + 'next': '/', + }) + self.assertRedirects(response, '/') + def test_authenticate(self): device = self.alice.staticdevice_set.get() token = device.token_set.get() diff -Nru python-django-otp-0.7.5/src/django_otp/util.py python-django-otp-0.8.1/src/django_otp/util.py --- python-django-otp-0.7.5/src/django_otp/util.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/util.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,10 +1,6 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - -from binascii import hexlify, unhexlify +from binascii import unhexlify from os import urandom -import six - from django.core.exceptions import ValidationError @@ -16,7 +12,7 @@ def key_validator(value): return hex_validator(20)(value) - key = models.CharField(max_length=40, validators=[key_validator], help_text=u'A hex-encoded 20-byte secret key') + key = models.CharField(max_length=40, validators=[key_validator], help_text='A hex-encoded 20-byte secret key') :param int length: If greater than 0, validation will fail unless the decoded value is exactly this number of bytes. @@ -36,7 +32,7 @@ """ def _validator(value): try: - if isinstance(value, six.text_type): + if isinstance(value, str): value = value.encode() unhexlify(value) @@ -58,7 +54,7 @@ :param int length: The number of (decoded) bytes to return. :returns: A string of hex digits. - :rtype: bytes + :rtype: str """ - return hexlify(urandom(length)) + return urandom(length).hex() diff -Nru python-django-otp-0.7.5/src/django_otp/views.py python-django-otp-0.8.1/src/django_otp/views.py --- python-django-otp-0.7.5/src/django_otp/views.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/src/django_otp/views.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from functools import partial from django.contrib.auth import BACKEND_SESSION_KEY @@ -42,7 +40,7 @@ if not hasattr(user, 'backend'): user.backend = self.request.session[BACKEND_SESSION_KEY] - return super(LoginView, self).form_valid(form) + return super().form_valid(form) # Backwards compatibility. diff -Nru python-django-otp-0.7.5/test/test_project/backends.py python-django-otp-0.8.1/test/test_project/backends.py --- python-django-otp-0.7.5/test/test_project/backends.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/test/test_project/backends.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,7 +1,4 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - - -class DummyBackend(object): +class DummyBackend: def authenticate(self, request): return None diff -Nru python-django-otp-0.7.5/test/test_project/settings.py python-django-otp-0.8.1/test/test_project/settings.py --- python-django-otp-0.7.5/test/test_project/settings.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/test/test_project/settings.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,7 +1,5 @@ # django-otp test project -from __future__ import absolute_import, division, print_function, unicode_literals - from os.path import abspath, dirname, join diff -Nru python-django-otp-0.7.5/test/test_project/urls.py python-django-otp-0.8.1/test/test_project/urls.py --- python-django-otp-0.7.5/test/test_project/urls.py 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/test/test_project/urls.py 2020-02-08 16:26:28.000000000 +0000 @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - from django.conf.urls import url from django.contrib import admin import django.contrib.auth.views @@ -7,6 +5,12 @@ from django.views.generic.base import View import django_otp.views +from django_otp.admin import OTPAdminSite + + +otp_admin_site = OTPAdminSite(OTPAdminSite.name) +for model_cls, model_admin in admin.site._registry.items(): + otp_admin_site.register(model_cls, model_admin.__class__) class HomeView(View): @@ -18,5 +22,5 @@ url(r'^$', HomeView.as_view()), url(r'^login/$', django_otp.views.LoginView.as_view()), url(r'^logout/$', django.contrib.auth.views.LogoutView.as_view()), - url(r'^admin/', admin.site.urls), + url(r'^admin/', otp_admin_site.urls), ] diff -Nru python-django-otp-0.7.5/tox.ini python-django-otp-0.8.1/tox.ini --- python-django-otp-0.7.5/tox.ini 2019-12-27 23:01:48.000000000 +0000 +++ python-django-otp-0.8.1/tox.ini 2020-02-08 16:26:28.000000000 +0000 @@ -1,6 +1,6 @@ [tox] envlist = static - py{27,37}-django111 + py{37}-django111 py{35,36}-django22 py{37,38}-django30 coverage @@ -9,7 +9,6 @@ setenv = PYTHONPATH = {env:PYTHONPATH:}{:}{toxinidir}/test DJANGO_SETTINGS_MODULE = test_project.settings deps = freezegun - mock ; python_version < "3.0" django111: Django==1.11.* django22: Django==2.2.* django30: Django==3.0.*