Accounts: add password change form to settings
All checks were successful
Build and push Docker image / build (push) Successful in 57s
Test / test (push) Successful in 15s

Uses Django's built-in PasswordChangeForm and update_session_auth_hash
so the session stays valid after the change. Form is hidden in a
<details> element and opens automatically on validation errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
marwin 2026-05-25 16:26:07 +02:00
parent c064d0d4e1
commit e3a9c61c05
3 changed files with 43 additions and 3 deletions

View file

@ -13,4 +13,5 @@ urlpatterns = [
path('background/upload/', views.upload_background, name='upload_background'), path('background/upload/', views.upload_background, name='upload_background'),
path('background/delete/', views.delete_background, name='delete_background'), path('background/delete/', views.delete_background, name='delete_background'),
path('focus-station/', views.save_focus_station, name='save_focus_station'), path('focus-station/', views.save_focus_station, name='save_focus_station'),
path('change-password/', views.change_password, name='change_password'),
] ]

View file

@ -1,9 +1,9 @@
import base64 import base64
import json import json
from django.conf import settings from django.conf import settings
from django.contrib.auth import authenticate, login, get_user_model from django.contrib.auth import authenticate, login, get_user_model, update_session_auth_hash
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, PasswordChangeForm
from django.http import JsonResponse from django.http import JsonResponse
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
@ -71,6 +71,7 @@ def settings_view(request):
context = { context = {
'profile': profile, 'profile': profile,
'has_lastfm': profile.has_lastfm(), 'has_lastfm': profile.has_lastfm(),
'password_form': PasswordChangeForm(request.user),
} }
return render(request, 'accounts/settings.html', context) return render(request, 'accounts/settings.html', context)
@ -181,6 +182,23 @@ def save_focus_station(request):
return JsonResponse({'ok': True}) return JsonResponse({'ok': True})
@login_required
@require_http_methods(['POST'])
def change_password(request):
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user)
return redirect('settings')
profile = request.user.profile
return render(request, 'accounts/settings.html', {
'profile': profile,
'has_lastfm': profile.has_lastfm(),
'password_form': form,
'password_form_open': True,
})
@login_required @login_required
@require_http_methods(['POST']) @require_http_methods(['POST'])
def lastfm_disconnect(request): def lastfm_disconnect(request):

View file

@ -72,7 +72,28 @@
<section class="settings-section"> <section class="settings-section">
<h2>Account</h2> <h2>Account</h2>
<p>Logged in as <strong>{{ request.user.username }}</strong></p> <p>Logged in as <strong>{{ request.user.username }}</strong></p>
<form method="post" action="{% url 'logout' %}" class="inline-form">
<details {% if password_form_open %}open{% endif %} style="margin-top:1rem;">
<summary class="btn" style="display:inline-block;cursor:pointer;">Change password</summary>
<form method="post" action="{% url 'change_password' %}" style="margin-top:1rem; display:flex; flex-direction:column; gap:0.6rem; max-width:320px;">
{% csrf_token %}
{% for field in password_form %}
<div>
<label style="display:block; font-size:0.85rem; margin-bottom:2px;">{{ field.label }}</label>
{{ field }}
{% if field.errors %}
<div class="message message-error" style="margin-top:4px; padding:4px 8px; font-size:0.8rem;">{{ field.errors|join:", " }}</div>
{% endif %}
</div>
{% endfor %}
{% if password_form.non_field_errors %}
<div class="message message-error" style="padding:4px 8px; font-size:0.8rem;">{{ password_form.non_field_errors|join:", " }}</div>
{% endif %}
<div><button type="submit" class="btn">Save</button></div>
</form>
</details>
<form method="post" action="{% url 'logout' %}" class="inline-form" style="margin-top:1rem;">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn">Logout</button> <button type="submit" class="btn">Logout</button>
</form> </form>