diff --git a/docs/index.md b/docs/index.md
index 3eb4b24..cdf0b46 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -5,6 +5,7 @@ authors: ["trent"]
# Trent's Blog
## **Posts By Date**
+* [2021-04-19: Test QRCODE Svg in Django](posts/test-qr-svg-django.md){target=_blank}
* [2021-01-25: Prosody Photo Uploads](posts/prosody-photo-uploads.md){target=_blank}
* [2021-01-09: Xmpp Apt Notifications](posts/xmpp-apt-notifications.md){target=_blank}
* [2020-12-20: Apache Virtual Hosts](posts/apache-virtual-hosts.md){target=_blank}
diff --git a/docs/links.md b/docs/links.md
index d2d5472..75a4a9d 100644
--- a/docs/links.md
+++ b/docs/links.md
@@ -7,6 +7,7 @@ authors: ["trent"]
## **Links**
* [Home](index.md){target=_blank}
+* [AudioBooks](https://trentpalmer.org){target=_blank}
* [GitHub](https://github.com/TrentSPalmer){target=_blank}
* [Twitter](https://twitter.com/boringtrent){target=_blank}
* [Facebook](https://www.facebook.com/trentspalmer){target=_blank}
diff --git a/docs/photos/IMG_screenshot_enable_totp_confirmation_form.jpg b/docs/photos/IMG_screenshot_enable_totp_confirmation_form.jpg
new file mode 100644
index 0000000..a05af87
Binary files /dev/null and b/docs/photos/IMG_screenshot_enable_totp_confirmation_form.jpg differ
diff --git a/docs/posts/test-qr-svg-django.md b/docs/posts/test-qr-svg-django.md
new file mode 100644
index 0000000..d66b05d
--- /dev/null
+++ b/docs/posts/test-qr-svg-django.md
@@ -0,0 +1,368 @@
+---
+title: "Test QRCODE Svg in Django"
+date: 2021-04-19
+draft: false
+tags: ["django", "python", "testing", "pillow", "beautifulsoup", "opencv", "cairosvg", "pyotp"]
+authors: ["trent"]
+---
+date: 2021-04-19
+
+## **Introduction**
+I worked out a solution in django-testing, for testing a view that renders a qrcode
+as an svg as an inline svg xml string.
+
+In case you are not familiar with
+[svg](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics){target=_blank},
+scalable vector graphics, it is an image that is
+completely rendered from a
+[string](https://en.wikipedia.org/wiki/String_(computer_science)){target=_blank}
+of text, or more accurately [XML](https://en.wikipedia.org/wiki/XML){target=_blank}
+text, unlike [PNG](https://en.wikipedia.org/wiki/Portable_Network_Graphics){target=_blank}
+or [JPG](https://en.wikipedia.org/wiki/JPEG){target=_blank}, which are
+[binary file](https://en.wikipedia.org/wiki/Binary_file){target=_blank}s.
+
+### Python Libraries Used
+* [BeautifulSoup](https://pypi.org/project/beautifulsoup4/){target=_blank}
+ to consume html
+* [CairoSVG](https://pypi.org/project/CairoSVG/){target=_blank}
+ to convert svg to png
+* [python-pillow](https://pypi.org/project/Pillow/){target=_blank}
+ to convert transparent png background to white
+* [opencv-python](https://pypi.org/project/opencv-python/){target=_blank}
+ to extract data from qrcode
+* [python-pyotp](https://pypi.org/project/pyotp/){target=_blank}
+
+## **Form ScreenShot**
+This is the form we will be testing.
+
+In real life you would confirm that you want to use
+[two-factor-authentication](https://en.wikipedia.org/wiki/Multi-factor_authentication){target=_blank}
+by scanning the qrcode with an
+[authentication app](https://play.google.com/store/apps/details?id=org.shadowice.flocke.andotp){target=_blank}
+on your smartphone, and then enter the
+[time-based one-time password](https://en.wikipedia.org/wiki/Time-based_One-Time_Password){target=_blank}.
+
+totp codes are derived from two things and two things only. A secret key and a time such as the current time.
+For instance, an authentication application can tell you what your totp code is, but of course
+in this testing scenario we use
+[python-pyotp](https://pypi.org/project/pyotp/){target=_blank}, after extracting
+the secret key from the qrcode.
+
+
+ enable totp confirmation form
+
+
+!["ScreenShot of a confirmation form for enabling totp (in a django app)"](
+ ../../photos/IMG_screenshot_enable_totp_confirmation_form.jpg){: .center }
+
+## **Import Python Libraries**
+```python
+# tp/accounts/tests/test_enable_totp_view.py
+# python manage.py test accounts.tests.test_enable_totp_view
+from django.test import TestCase
+from django.contrib.auth.models import User
+from accounts.models import Account
+from django.urls import reverse
+from bs4 import BeautifulSoup
+import cv2
+from cairosvg import svg2png
+from PIL import Image
+import pyotp
+import pathlib
+```
+
+The Account model has a one-to-one relationship with the Django built-in
+User model. This is where we keep track of the totp secret key and
+boolean value for having totp authentication enabled.
+
+## **setUp TestCase**
+```python
+...
+import pathlib
+
+
+class TestEnableTOTPViewTestCase(TestCase):
+
+ def setUp(self):
+ user_a = User.objects.create(
+ username='user_a')
+ user_a.set_password('password_user_a')
+ user_a.save()
+ Account.objects.create(user=user_a)
+```
+
+We create a test user and save that in the test database.
+
+## **GET Form**
+```python
+class TestEnableTOTPViewTestCase(TestCase):
+ ...
+ def test_enable_totp_view(self):
+ self.client.login(
+ username='user_a',
+ password='password_user_a'
+ )
+ get_response = self.client.get(
+ reverse('accounts:enable_totp')
+ )
+ self.assertEquals(
+ get_response.status_code, 200
+ )
+ self.assertTemplateUsed(
+ get_response, 'accounts/totp_form.html'
+ )
+ self.assertEquals(
+ get_response.request['PATH_INFO'],
+ '/accounts/enable-totp/'
+ )
+```
+The TestCase requires two requests. In the first request we GET the form
+so that we can consume the qrcode.
+
+## **Consume SVG with BeautifulSoup**
+```python
+class TestEnableTOTPViewTestCase(TestCase):
+ ...
+ def test_enable_totp_view(self):
+ ...
+ soup = BeautifulSoup(
+ get_response.content, features="lxml"
+ )
+ svg_container = soup.find(
+ "div", {"id": "svgcontainer"}
+ )
+ self.assertIsNotNone(svg_container)
+ scsvg = svg_container.findChild(
+ "svg", recursive=False
+ )
+ self.assertIsNotNone(scsvg)
+
+ with open('qr.svg', 'w') as f:
+ x_string = "\n"
+ f.write(x_string + str(scsvg))
+```
+The inline xml for the svg comes to us as a child of a
+`div` with an `id` of *svgcontainer*. We capture the xml of the svg in the variable
+`scsvg`, and then write it out to disc.
+
+## **svg2png**
+```python
+class TestEnableTOTPViewTestCase(TestCase):
+ ...
+ def test_enable_totp_view(self):
+ ...
+ svg2png(
+ url='qr.svg', write_to='qr.png',
+ scale=8
+ )
+```
+With svg2png from CairoSVG, we convert the svg to png format. Opencv seems
+unable to consume the qrcode unless you scale it up.
+
+## **Add White Background**
+```python
+class TestEnableTOTPViewTestCase(TestCase):
+ ...
+ def test_enable_totp_view(self):
+ ...
+ t_image = Image.open('qr.png')
+ t_image.load()
+ background = Image.new(
+ "RGB", t_image.size,
+ (255, 255, 255)
+ )
+ background.paste(
+ t_image,
+ mask=t_image.split()[3]
+ )
+ background.save(
+ 'qr.jpg', "JPEG",
+ quality=100
+ )
+```
+We use Image from python-pillow to change the background from transparent
+to white. Opencv seems unable to consume the qrcode when it has a transparent
+background.
+
+## **Extract Data From QRCODE**
+```python
+class TestEnableTOTPViewTestCase(TestCase):
+ ...
+ def test_enable_totp_view(self):
+ ...
+ image = cv2.imread('qr.jpg')
+ qr_det = cv2.QRCodeDetector()
+ qrdata = qr_det.detectAndDecode(image)
+ totp_code = pyotp.TOTP(
+ pyotp.parse_uri(qrdata[0]).secret
+ ).now()
+```
+
+* `qrdata[0]` will be the *otpauth_uri*.
+* `pyotp.parse_uri(qrdata[0]).secret` is the secret key.
+* `totp_code` is the one-time password.
+
+## **POST the `totp_code`**
+```python
+class TestEnableTOTPViewTestCase(TestCase):
+ ...
+ def test_enable_totp_view(self):
+ ...
+ totp_code = pyotp.TOTP(
+ pyotp.parse_uri(qrdata[0]).secret
+ ).now()
+ response = self.client.post(
+ reverse('accounts:enable_totp'),
+ {'totp_code': totp_code},
+ follow=True
+ )
+ self.assertEquals(
+ response.status_code, 200
+ )
+ self.assertTemplateUsed(
+ response, 'base_form.html'
+ )
+ self.assertEquals(
+ response.request['PATH_INFO'],
+ '/accounts/edit-profile/'
+ )
+ user_a = User.objects.get(
+ username='user_a'
+ )
+ self.assertTrue(user_a.account.use_totp)
+ self.assertEquals(
+ len(user_a.account.totp_key), 16
+ )
+ pathlib.Path('qr.svg').unlink()
+ pathlib.Path('qr.png').unlink()
+ pathlib.Path('qr.jpg').unlink()
+```
+Post the `totp_code` back to the form, and then verify
+that the database is updated, and then delete the image
+files.
+
+## **Complete TestCase**
+```python
+# tp/accounts/tests/test_enable_totp_view.py
+# python manage.py test accounts.tests.test_enable_totp_view
+from django.test import TestCase
+from django.contrib.auth.models import User
+from accounts.models import Account
+from django.urls import reverse
+from bs4 import BeautifulSoup
+import cv2
+from cairosvg import svg2png
+from PIL import Image
+import pyotp
+
+
+class TestEnableTOTPViewTestCase(TestCase):
+
+ # setUP TestCase
+ def setUp(self):
+ user_a = User.objects.create(
+ username='user_a')
+ user_a.set_password('password_user_a')
+ user_a.save()
+ Account.objects.create(user=user_a)
+
+ def test_enable_totp_view(self):
+ self.client.login(
+ username='user_a',
+ password='password_user_a'
+ )
+
+ # GET Form
+ get_response = self.client.get(
+ reverse('accounts:enable_totp')
+ )
+ self.assertEquals(
+ get_response.status_code, 200
+ )
+ self.assertTemplateUsed(
+ get_response, 'accounts/totp_form.html'
+ )
+ self.assertEquals(
+ get_response.request['PATH_INFO'],
+ '/accounts/enable-totp/'
+ )
+
+ # Consume SVG with BeautifulSoup
+ soup = BeautifulSoup(
+ get_response.content, features="lxml"
+ )
+ svg_container = soup.find(
+ "div", {"id": "svgcontainer"}
+ )
+ self.assertIsNotNone(svg_container)
+ scsvg = svg_container.findChild(
+ "svg", recursive=False
+ )
+ self.assertIsNotNone(scsvg)
+
+ with open('qr.svg', 'w') as f:
+ x_string = "\n"
+ f.write(x_string + str(scsvg))
+
+ # svg2png
+ svg2png(
+ url='qr.svg', write_to='qr.png',
+ scale=8
+ )
+
+ # add white background
+ t_image = Image.open('qr.png')
+ t_image.load()
+ background = Image.new(
+ "RGB", t_image.size,
+ (255, 255, 255)
+ )
+ background.paste(
+ t_image,
+ mask=t_image.split()[3]
+ )
+ background.save(
+ 'qr.jpg', "JPEG",
+ quality=100
+ )
+
+ # extract data from qrcode
+ image = cv2.imread('qr.jpg')
+ qr_det = cv2.QRCodeDetector()
+ qrdata = qr_det.detectAndDecode(image)
+ totp_code = pyotp.TOTP(
+ pyotp.parse_uri(qrdata[0]).secret
+ ).now()
+ totp_code = pyotp.TOTP(
+ pyotp.parse_uri(qrdata[0]).secret
+ ).now()
+
+ # POST the `totp_code`
+ response = self.client.post(
+ reverse('accounts:enable_totp'),
+ {'totp_code': totp_code},
+ follow=True
+ )
+ self.assertEquals(
+ response.status_code, 200
+ )
+ self.assertTemplateUsed(
+ response, 'base_form.html'
+ )
+ self.assertEquals(
+ response.request['PATH_INFO'],
+ '/accounts/edit-profile/'
+ )
+ user_a = User.objects.get(
+ username='user_a'
+ )
+ self.assertTrue(user_a.account.use_totp)
+ self.assertEquals(
+ len(user_a.account.totp_key), 16
+ )
+ pathlib.Path('qr.svg').unlink()
+ pathlib.Path('qr.png').unlink()
+ pathlib.Path('qr.jpg').unlink()
+```
diff --git a/mkdocs.yml b/mkdocs.yml
index 6b083e6..618e85f 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -19,6 +19,7 @@ markdown_extensions:
nav:
- Home:
- Home: index.md
+ - posts/test-qr-svg-django.md
- posts/prosody-photo-uploads.md
- posts/xmpp-apt-notifications.md
- posts/apache-virtual-hosts.md