Fischer, Part 4: Configuring Django Authentication
Today I'd like to experiment with some nice interfaces for tagging and showing tagged bookmarks. But it feels too clunky to log into the django admin then navigate back, so first I want to get Django authentication set up. I suspect that if this turns into something I want to share with others, I'll eventually wind up with allauth, but that feels too heavy for now. And setting up Django's built-in authentication isn't hard.
Login and Logout Views
Since there’s no list of URLs being explicitly defined, the easiest way to add login and logout views to the list of routes maintained by the Django
object is to create trivial subclasses of the built-in views and decorate them witn @app.route()
.
from django.contrib.auth import views as auth_views
from django.contrib.auth.decorators import login_required
@app.route("login/", name="login")
class LoginView(auth_views.LoginView):
pass
@app.route("logout/", name="logout")
class LogoutView(auth_views.LogoutView):
pass
Then create a login template:
templates/registration/login.html
:
{% extends "bookmarks/base.html" %}
{% block title %}Fischer: Log-in{% endblock %}
{% block content %}
<h1>Sign in to use Fischer</h1>
{% if form.errors %}
<div class="alert alert-danger">{{form.non_field_errors}}</div>
{% endif %}
<div class="login-form container">
<form action="{% url 'login' %}" method="post">
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
{% csrf_token %}
<input type="hidden" name="next" value="{{ next }}" />
<p><input type="submit" value="Log-in"></p>
</form>
</div>
{% endblock %}
and add settings for login and logout redirect:
app = Django(
EXTRA_APPS=["taggit", "django_extensions", "import_export"],
TAGGIT_CASE_INSENSITIVE=True,
LOGIN_URL="login",
LOGOUT_URL="logout",
LOGIN_REDIRECT_URL="index",
LOGOUT_REDIRECT_URL="index",
)
Finally, I can get rid of the manual authentication check and redirection in my views. For my index page, I create a template for the unauthenticated user in templates/bookmarks/landing.html
:
{% extends "bookmarks/base.html" %}
{% block content %}
<p>Bookmark management and browser start page</p>
<p>You must be logged in to view your bookmarks.</p>
{% endblock %}
And render it in my index view:
@app.route("/")
def index(request):
if not request.user.is_authenticated:
return render(request, "bookmarks/landing.html")
return redirect("user_page", request.user.username)
I decorate my user page with @login_required
:
@login_required
@app.route("/u/<str:username>")
def user_page(request, username: str):
if request.user.username != username:
raise PermissionDenied(
f"{request.user.username} may not view bookmarks for {username}"
)
bookmarks = request.user.bookmarks.all()
return render(request, "bookmarks/user.html", {"bookmarks": bookmarks})
I add a little navigation bar with a Sign Out button:
<nav>
<h1>Fischer</h1>
<ul>
{% if user.is_authenticated %}
<li>
<a href="#">Manage Bookmarks</a>
</li>
<li>
<a href="#">Start Page</a>
</li>
{% if user.is_staff %}
<li>
<a href="{% url 'admin:index' %}">Admin</a>
</li>
{% endif %}
<li>
<form action="{% url 'logout' %}" method="post">
{% csrf_token %}
<button role="submit">Sign Out</button>
</form>
</li>
{% else %}
<li>
<a role="button" href="{% url 'login' %}">Sign In</a>
</li>
{% endif %}
</ul>
</nav>
And I reference it from my base template:
{% load static %}
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<link rel="stylesheet" href="{% static 'css/pico.min.css' %}">
<link rel="stylesheet" href="{% static 'css/alerts.min.css' %}">
<title>{% block title %}Fischer{% endblock %}</title>
</head>
<body>
<main class="container">
{% include 'bookmarks/nav.html' %}
{% block content %}
<h1>Fischer</h1>
{% endblock %}
</main>
</body>
</html>
The pages don’t look great yet, but that’s not a priority. They’re quite usable, and when I want them to look nice, I’ll have moved to tailwind for more control over the styling anyway. There’s no point in attempting to gain that much control over them with Pico CSS for me.
So far, I’m enjoying how quickly nanodjango lets me kick the tires on my idea with less boilerplate than usual. I know I’ll need to convert the project at some point, but, for the moment, this feels nice.
If you want to follow along with my progress, you can get the code here.