From 3e9c0f6b486fc627f07c8ef723bc3def2254357c Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 16 Apr 2024 19:32:15 +0300 Subject: [PATCH] Add support for `segno` as alternative to `qrcode` for QR image generation Fix #141. --- CHANGES.rst | 13 ++++++++++++ docs/source/overview.rst | 5 +++-- pyproject.toml | 4 ++++ src/django_otp/plugins/otp_hotp/admin.py | 9 ++------ .../templates/otp_hotp/admin/config.html | 2 +- src/django_otp/plugins/otp_totp/admin.py | 9 ++------ .../templates/otp_totp/admin/config.html | 2 +- src/django_otp/qr.py | 21 +++++++++++++++++++ 8 files changed, 47 insertions(+), 18 deletions(-) create mode 100644 src/django_otp/qr.py diff --git a/CHANGES.rst b/CHANGES.rst index de33b269..c083faf3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,16 @@ +Pending +-------------------------------------------------------------------------------- + +- `#141`_: Support alternative QR code library `segno`_. + + Previously, only the `qrcode`_ library was supported. + + Use ``segno`` by installing ``django-otp[segno]`` or just install the + ``segno`` package. + +.. _#141: https://github.com/django-otp/django-otp/issues/141 +.. _segno: https://pypi.python.org/pypi/segno/ + v1.4.1 - April 10, 2024 - Minor EmailDevice updates -------------------------------------------------------------------------------- diff --git a/docs/source/overview.rst b/docs/source/overview.rst index 081b61fd..4fdd8092 100644 --- a/docs/source/overview.rst +++ b/docs/source/overview.rst @@ -211,12 +211,13 @@ django-otp includes support for several standard device types. :class:`~django_otp.plugins.otp_totp.models.TOTPDevice` handle standard OTP algorithms, which can be used with a variety of OTP generators. For example, it's easy to pair these devices with `Google Authenticator`_ using the `otpauth -URL scheme`_. If you have the `qrcode`_ package installed, the admin interface -will generate QR Codes for you. +URL scheme`_. If you have either the `segno`_ or `qrcode`_ packages installed, +the admin interface will generate QR Codes for you. .. _Google Authenticator: https://github.com/google/google-authenticator .. _otpauth URL scheme: https://github.com/google/google-authenticator/wiki/Key-Uri-Format +.. _segno: https://pypi.python.org/pypi/segno/ .. _qrcode: https://pypi.python.org/pypi/qrcode/ diff --git a/pyproject.toml b/pyproject.toml index 8719e3e0..3086c268 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,9 @@ dependencies = [ ] [project.optional-dependencies] +segno = [ + "segno", +] qrcode = [ "qrcode", ] @@ -38,6 +41,7 @@ Documentation = "https://django-otp-official.readthedocs.io/" [tool.hatch.envs.default] features = [ + "segno", "qrcode", ] dependencies = [ diff --git a/src/django_otp/plugins/otp_hotp/admin.py b/src/django_otp/plugins/otp_hotp/admin.py index 861c6998..90ddf2dc 100644 --- a/src/django_otp/plugins/otp_hotp/admin.py +++ b/src/django_otp/plugins/otp_hotp/admin.py @@ -7,6 +7,7 @@ from django.utils.html import format_html from django_otp.conf import settings +from django_otp.qrcode import write_qrcode_image from .models import HOTPDevice @@ -150,14 +151,8 @@ def qrcode_view(self, request, pk): raise PermissionDenied() try: - import qrcode - import qrcode.image.svg - - img = qrcode.make( - device.config_url, image_factory=qrcode.image.svg.SvgImage - ) response = HttpResponse(content_type='image/svg+xml') - img.save(response) + write_qrcode_image(device.config_url, response) except ImportError: response = HttpResponse('', status=503) diff --git a/src/django_otp/plugins/otp_hotp/templates/otp_hotp/admin/config.html b/src/django_otp/plugins/otp_hotp/templates/otp_hotp/admin/config.html index cbc43346..37c9d221 100644 --- a/src/django_otp/plugins/otp_hotp/templates/otp_hotp/admin/config.html +++ b/src/django_otp/plugins/otp_hotp/templates/otp_hotp/admin/config.html @@ -10,7 +10,7 @@ >

{{ device.config_url }}

diff --git a/src/django_otp/plugins/otp_totp/admin.py b/src/django_otp/plugins/otp_totp/admin.py index 836d889b..9d80e555 100644 --- a/src/django_otp/plugins/otp_totp/admin.py +++ b/src/django_otp/plugins/otp_totp/admin.py @@ -7,6 +7,7 @@ from django.utils.html import format_html from django_otp.conf import settings +from django_otp.qr import write_qrcode_image from .models import TOTPDevice @@ -150,14 +151,8 @@ def qrcode_view(self, request, pk): raise PermissionDenied() try: - import qrcode - import qrcode.image.svg - - img = qrcode.make( - device.config_url, image_factory=qrcode.image.svg.SvgImage - ) response = HttpResponse(content_type='image/svg+xml') - img.save(response) + write_qrcode_image(device.config_url, response) except ImportError: response = HttpResponse('', status=503) diff --git a/src/django_otp/plugins/otp_totp/templates/otp_totp/admin/config.html b/src/django_otp/plugins/otp_totp/templates/otp_totp/admin/config.html index 8350a95a..aeb14c6d 100644 --- a/src/django_otp/plugins/otp_totp/templates/otp_totp/admin/config.html +++ b/src/django_otp/plugins/otp_totp/templates/otp_totp/admin/config.html @@ -10,7 +10,7 @@ >

{{ device.config_url }}

diff --git a/src/django_otp/qr.py b/src/django_otp/qr.py new file mode 100644 index 00000000..0db982f5 --- /dev/null +++ b/src/django_otp/qr.py @@ -0,0 +1,21 @@ +def write_qrcode_image(data, out): + """Write a QR code image for data to out. + + The written image is in image/svg+xml format. + + One of `qrcode` or `segno` are required. If neither is found, raises + ModuleNotFoundError. + """ + try: + import qrcode + import qrcode.image.svg + + img = qrcode.make( + data, image_factory=qrcode.image.svg.SvgImage + ) + img.save(out) + except ModuleNotFoundError: + import segno + + img = segno.make(data) + img.save(out, kind='svg')