working FileIndex and user Profile pages/settings

This commit is contained in:
2026-08-03 14:13:40 -05:00
parent 85e6241ad4
commit 39e31d3980
17 changed files with 999 additions and 25 deletions
+137 -4
View File
@@ -1,11 +1,18 @@
import hashlib
import io
import secrets
from django.contrib import messages
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth import authenticate, get_user_model, login, logout, update_session_auth_hash
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.shortcuts import redirect, render
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm, UserCreationForm
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.shortcuts import get_object_or_404, redirect, render
from library.models import FileIndex
from .forms import AvatarForm, BioForm, EmailForm, UsernameForm
from .models import ApiToken, UserProfile
@@ -37,7 +44,12 @@ def user_login(request):
def user_logout(request):
if request.method == 'POST':
was_authorized = request.session.get('authorized', False)
logout(request)
# logout() flushes the session (wiping the gate flag). The gate is a
# site-wide master password, separate from user accounts — preserve it.
if was_authorized:
request.session['authorized'] = True
return redirect('landing:home')
return redirect('landing:home')
@@ -67,4 +79,125 @@ def token_create(request):
@login_required
def token_delete(request, token_id):
ApiToken.objects.filter(id=token_id, user=request.user).delete()
return redirect('profiles:settings')
return redirect('profiles:settings')
def _jpeg_bytes(uploaded):
"""Return JPEG bytes for an uploaded image.
Already-JPEG uploads are stored as-is; anything else is re-encoded
to JPEG via Pillow.
"""
import PIL.Image
image = PIL.Image.open(uploaded)
if image.format == 'JPEG':
uploaded.seek(0)
return uploaded.read()
if image.mode not in ('RGB', 'L'):
image = image.convert('RGB')
buffer = io.BytesIO()
image.save(buffer, format='JPEG', quality=88)
return buffer.getvalue()
def _store_avatar(user, uploaded):
"""Persist an avatar under /avatars/user_<id>/picture.jpeg, index it in
FileIndex, and return the FileIndex row (replacing any previous avatar)."""
profile, _ = UserProfile.objects.get_or_create(user=user)
old_index = profile.avatar
if old_index is not None:
if old_index.stored_path and default_storage.exists(old_index.stored_path):
default_storage.delete(old_index.stored_path)
FileIndex.objects.filter(pk=old_index.pk).delete()
profile.avatar = None
data = _jpeg_bytes(uploaded)
stored_path = f'avatars/user_{user.pk}/picture.jpeg'
actual_path = default_storage.save(stored_path, ContentFile(data))
file_index = FileIndex.objects.create(
owner=user,
kind='avatar',
stored_path=actual_path,
original_filename=getattr(uploaded, 'name', '') or 'picture.jpeg',
content_type='image/jpeg',
size=len(data),
md5=hashlib.md5(data).hexdigest(),
)
profile.avatar = file_index
profile.save(update_fields=['avatar'])
return file_index
@login_required
def account_page(request):
username_form = UsernameForm(instance=request.user)
email_form = EmailForm(instance=request.user)
password_form = PasswordChangeForm(request.user)
avatar_form = AvatarForm()
if request.method == 'POST':
action = request.POST.get('action')
if action == 'username':
username_form = UsernameForm(request.POST, instance=request.user)
if username_form.is_valid():
username_form.save()
messages.success(request, 'Username updated.')
return redirect('profiles:account')
messages.error(request, 'Could not update username.')
elif action == 'email':
email_form = EmailForm(request.POST, instance=request.user)
if email_form.is_valid():
email_form.save()
messages.success(request, 'Email updated.')
return redirect('profiles:account')
messages.error(request, 'Could not update email.')
elif action == 'password':
password_form = PasswordChangeForm(request.user, request.POST)
if password_form.is_valid():
user = password_form.save()
update_session_auth_hash(request, user)
messages.success(request, 'Password updated.')
return redirect('profiles:account')
messages.error(request, 'Could not update password.')
elif action == 'avatar':
avatar_form = AvatarForm(request.POST, request.FILES)
if avatar_form.is_valid():
_store_avatar(request.user, avatar_form.cleaned_data['avatar'])
messages.success(request, 'Profile picture updated.')
return redirect('profiles:account')
messages.error(request, 'Could not update profile picture.')
context = {
'username_form': username_form,
'email_form': email_form,
'password_form': password_form,
'avatar_form': avatar_form,
}
return render(request, 'profiles/account.html', context)
def user_profile(request, username):
User = get_user_model()
user = get_object_or_404(User, username__iexact=username)
profile, created = UserProfile.objects.get_or_create(user=user)
is_owner = request.user.is_authenticated and request.user.pk == user.pk
bio_form = None
if is_owner:
if request.method == 'POST' and request.POST.get('action') == 'bio':
bio_form = BioForm(request.POST, instance=profile)
if bio_form.is_valid():
bio_form.save()
messages.success(request, 'Bio updated.')
return redirect('profiles:user_profile', username=user.username)
messages.error(request, 'Could not update bio.')
else:
bio_form = BioForm(instance=profile)
return render(request, 'profiles/profile.html', {
'profile': profile,
'profile_user': user,
'is_owner': is_owner,
'bio_form': bio_form,
})