Skip to content

Commit bfb5de6

Browse files
committed
Initial set of examples from the first 1/3 of the article
1 parent ea903b4 commit bfb5de6

File tree

16 files changed

+170
-3
lines changed

16 files changed

+170
-3
lines changed

config/django/base.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import os
1414

15-
from config.env import BASE_DIR, env
15+
from config.env import BASE_DIR, APPS_DIR, env
1616

1717
env.read_env(os.path.join(BASE_DIR, ".env"))
1818

@@ -54,7 +54,8 @@
5454
]
5555

5656
INSTALLED_APPS = [
57-
"django.contrib.admin",
57+
# "django.contrib.admin",
58+
"styleguide_example.admin.apps.AdminConfig",
5859
"django.contrib.auth",
5960
"django.contrib.contenttypes",
6061
"django.contrib.sessions",
@@ -80,10 +81,12 @@
8081

8182
ROOT_URLCONF = "config.urls"
8283

84+
print(os.path.join(APPS_DIR, "templates"))
85+
8386
TEMPLATES = [
8487
{
8588
"BACKEND": "django.template.backends.django.DjangoTemplates",
86-
"DIRS": [],
89+
"DIRS": [os.path.join(APPS_DIR, "templates")],
8790
"APP_DIRS": True,
8891
"OPTIONS": {
8992
"context_processors": [

config/env.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
env = environ.Env()
55

66
BASE_DIR = environ.Path(__file__) - 2
7+
APPS_DIR = BASE_DIR.path("styleguide_example")
78

89

910
def env_to_enum(enum_cls, value):

requirements/base.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@ google-api-python-client==2.86.0
2828
google-auth==2.21.0
2929
google-auth-httplib2==0.1.0
3030
google-auth-oauthlib==1.0.0
31+
32+
pyotp==2.8.0
33+
qrcode==7.4.2

styleguide_example/admin/__init__.py

Whitespace-only changes.

styleguide_example/admin/apps.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django.contrib.admin.apps import AdminConfig as BaseAdminConfig
2+
3+
4+
class AdminConfig(BaseAdminConfig):
5+
default_site = "styleguide_example.admin.sites.AdminSite"

styleguide_example/admin/migrations/__init__.py

Whitespace-only changes.

styleguide_example/admin/sites.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from django.contrib import admin
2+
from django.urls import path
3+
4+
from styleguide_example.blog_examples.admin_2fa.views import AdminSetupTwoFactorAuthView
5+
6+
7+
class AdminSite(admin.AdminSite):
8+
def get_urls(self):
9+
base_urlpatterns = super().get_urls()
10+
11+
extra_urlpatterns = [
12+
path("setup-2fa/", self.admin_view(AdminSetupTwoFactorAuthView.as_view()), name="setup-2fa")
13+
]
14+
15+
return extra_urlpatterns + base_urlpatterns
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from django.contrib import admin
2+
from django.utils.html import format_html
3+
4+
from styleguide_example.blog_examples.admin_2fa.models import UserTwoFactorAuthData
5+
6+
7+
@admin.register(UserTwoFactorAuthData)
8+
class UserTwoFactorAuthDataAdmin(admin.ModelAdmin):
9+
def qr_code(self, obj):
10+
return format_html(obj.generate_qr_code())
11+
12+
def get_readonly_fields(self, request, obj=None):
13+
if obj is not None:
14+
return ["user", "otp_secret", "qr_code"]
15+
else:
16+
return ()

styleguide_example/blog_examples/admin_2fa/__init__.py

Whitespace-only changes.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from typing import Optional
2+
3+
import pyotp
4+
import qrcode
5+
import qrcode.image.svg
6+
from django.conf import settings
7+
from django.db import models
8+
9+
10+
class UserTwoFactorAuthData(models.Model):
11+
user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name="two_factor_auth_data", on_delete=models.CASCADE)
12+
13+
otp_secret = models.CharField(max_length=255)
14+
15+
def generate_qr_code(self, name: Optional[str] = None) -> str:
16+
totp = pyotp.TOTP(self.otp_secret)
17+
qr_uri = totp.provisioning_uri(name=name, issuer_name="Styleguide Example Admin 2FA Demo")
18+
19+
image_factory = qrcode.image.svg.SvgPathImage
20+
qr_code_image = qrcode.make(qr_uri, image_factory=image_factory)
21+
22+
# The result is going to be an HTML <svg> tag
23+
return qr_code_image.to_string().decode("utf_8")

0 commit comments

Comments
 (0)