From f7d473c055fbead4dbea07d9a1a591b5d64feb7d Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Wed, 8 Jul 2026 16:15:48 -0400 Subject: [PATCH 01/14] Init --- templates/v3/includes/_button.html | 2 +- templates/v3/user_profile_edit.html | 8 ++++++++ users/serializers.py | 1 - users/views.py | 20 ++++++++++++++++---- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/templates/v3/includes/_button.html b/templates/v3/includes/_button.html index 7469f5ebd..6b3a74b92 100644 --- a/templates/v3/includes/_button.html +++ b/templates/v3/includes/_button.html @@ -21,7 +21,7 @@ - icon-library (uses btn-icon-library styles; ignores btn + btn-* classes) - disabled (optional): if truthy, adds disabled attribute to button - alpine_disabled (optional): Alpine.js expression for dynamic disabled binding (e.g. "hasErrors") - - alpine_click (optional): Alpine.js expression run on click (e.g. "save()") + - alpine_click (optional): Alpine.js expression for a click handler (e.g. "save()"); button must sit inside an x-data scope - extra_classes (optional) : Any extra classes that the button should have, as a string - aria_label (optional): An optional label for assistive technology. Defaults to the button label - js_disabled (optional, boolean): If this value is true, the button is hidden unless Javascript is disabled on the page diff --git a/templates/v3/user_profile_edit.html b/templates/v3/user_profile_edit.html index ff95beff0..21bf2842c 100644 --- a/templates/v3/user_profile_edit.html +++ b/templates/v3/user_profile_edit.html @@ -420,6 +420,9 @@ const SLACK_PROFILE_URL_PREFIX = '{{ SLACK_PROFILE_URL_PREFIX|escapejs }}'; const SLACK_MEMBER_ID_PATTERN = /^[A-Z0-9]{9,11}$/i; + const avatarInput = document.querySelector("input[name='{{user_profile_form.avatar.name}}']"); + const avatarDelete = document.querySelector('#avatar-row__delete-avatar') + return { saving: false, saveStatus: '', // '' | 'saved' | 'error' (button shows "saving" via :disabled) @@ -510,6 +513,11 @@ }); if (hasError) return; + + let avatar = avatarInput.value + if(avatarDelete.checked) { + avatar = null; + } this.saving = true; this.saveStatus = ''; this.saveError = ''; diff --git a/users/serializers.py b/users/serializers.py index 280456aa1..d06bf3537 100644 --- a/users/serializers.py +++ b/users/serializers.py @@ -99,7 +99,6 @@ class Meta: read_only_fields = ( "id", "email", # Users shouldn't change their email this way - "profile_image", "date_joined", ) diff --git a/users/views.py b/users/views.py index 38b218274..83a1272cd 100644 --- a/users/views.py +++ b/users/views.py @@ -5,6 +5,7 @@ from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib import auth +from django.contrib.auth.models import AnonymousUser from django.contrib.messages.views import SuccessMessageMixin from django.http import HttpResponseRedirect from django.urls import reverse_lazy @@ -161,13 +162,15 @@ def get_v3_context_data(self, **kwargs): # Data shared between both versions, Boost Github and Mailing List activity ctx["github_activity_card_data"] = { "title": "Latest Boost Github activity", - "markdown_text": dedent(""" + "markdown_text": dedent( + """ * Created 24 Commits in [**7 repositories**](https://www.example.com) * Created [**1 repository**](https://www.example.com) * Created a pull request in [**cppalliance/buffers**](https://www.example.com) that received 6 comments * Opened 17 other pull requests in [**6 repositories**](https://www.example.com) * Reviewed 3 pull requests in [**3 repositories**](https://www.example.com) - """), + """ + ), "button_url": "https://www.github.com", "button_label": "View on Github", } @@ -203,7 +206,8 @@ def get_v3_context_data(self, **kwargs): } if self.request.GET.get("filled"): - ctx["bio"] = dedent(""" + ctx["bio"] = dedent( + """ **Professional Profile** I am a software engineer and C++ expert with extensive experience in systems programming and open-source software development. My work focuses on advancing the C++ ecosystem through libraries, tools, and community leadership. @@ -227,7 +231,8 @@ def get_v3_context_data(self, **kwargs): * Network Programming: High-performance asynchronous networking solutions in C++ These interests have shaped my contributions to the C++ ecosystem, particularly in developing libraries that make network programming more accessible and efficient for developers. - """) + """ + ) ctx["contributor_data"] = { "Author": ["Beast", "JSON"], "Maintainer": ["Beast", "Accumulator"], @@ -538,6 +543,13 @@ def update_profile(self, form, request): for error in form.errors.values(): messages.error(request, f"{error}") + def dispatch(self, request, *args, **kwargs): + if request.GET.get("edit", "").lower() == "true" and isinstance( + request.user, AnonymousUser + ): + return HttpResponseRedirect(reverse_lazy("account_login")) + return super().dispatch(request, *args, **kwargs) + # Custom Allauth Views From 2fae2bcb01e4767a241cd3584bd2257b72411027 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Thu, 9 Jul 2026 12:03:13 -0400 Subject: [PATCH 02/14] Continue work --- templates/v3/user_profile_edit.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/templates/v3/user_profile_edit.html b/templates/v3/user_profile_edit.html index 21bf2842c..485eee868 100644 --- a/templates/v3/user_profile_edit.html +++ b/templates/v3/user_profile_edit.html @@ -503,6 +503,7 @@ async save() { if (this.saving) return; + const formData = new FormData() // Validate all links up front; block the save if any fail. let hasError = false; @@ -512,6 +513,7 @@ if (msg) hasError = true; }); if (hasError) return; + formData.append('profile_links', this.collectLinks()) let avatar = avatarInput.value @@ -521,14 +523,15 @@ this.saving = true; this.saveStatus = ''; this.saveError = ''; + formData.append('profile_image', avatarInput.files[0]) try { const res = await fetch('/api/v1/users/me/', { method: 'PATCH', headers: { 'X-CSRFToken': '{{ csrf_token }}', - 'Content-Type': 'application/json', + 'Content-Type': 'multipart/form-data', }, - body: JSON.stringify({ profile_links: this.collectLinks() }), + body: formData, }); if (!res.ok) { let data = {}; From c01049ab338cdaedff006f77c373134374b0cd5f Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Thu, 9 Jul 2026 17:14:48 -0400 Subject: [PATCH 03/14] Complete Delete functionality --- templates/v3/user_profile_edit.html | 15 ++++++++++++--- users/views.py | 5 +++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/templates/v3/user_profile_edit.html b/templates/v3/user_profile_edit.html index 485eee868..0eca82282 100644 --- a/templates/v3/user_profile_edit.html +++ b/templates/v3/user_profile_edit.html @@ -421,7 +421,9 @@ const SLACK_MEMBER_ID_PATTERN = /^[A-Z0-9]{9,11}$/i; const avatarInput = document.querySelector("input[name='{{user_profile_form.avatar.name}}']"); - const avatarDelete = document.querySelector('#avatar-row__delete-avatar') + const avatarDelete = document.querySelector('#user-profile__avatar-delete-checkbox') + const avatarComponent = document.querySelector('#user-profile-editable-avatar') + const avatarImg = avatarComponent.querySelector('img') return { saving: false, @@ -520,16 +522,19 @@ if(avatarDelete.checked) { avatar = null; } + if(avatarInput.files.length > 0) formData.append('profile_image', avatarInput.files[0]) + formData.append('delete_profile_image', avatarDelete.checked) + this.saving = true; this.saveStatus = ''; this.saveError = ''; - formData.append('profile_image', avatarInput.files[0]) + + try { const res = await fetch('/api/v1/users/me/', { method: 'PATCH', headers: { 'X-CSRFToken': '{{ csrf_token }}', - 'Content-Type': 'multipart/form-data', }, body: formData, }); @@ -554,6 +559,10 @@ throw new Error(fieldErrors.length ? fieldErrors.join(' ') : `Save failed (${res.status})`); } this.saveStatus = 'saved'; + if(avatarDelete.checked) { + avatarDelete.checked = false; + avatarImg.src = ""; + } } catch (err) { console.error('Profile save failed:', err); this.saveStatus = 'error'; diff --git a/users/views.py b/users/views.py index 83a1272cd..336beaca3 100644 --- a/users/views.py +++ b/users/views.py @@ -79,6 +79,11 @@ class CurrentUserAPIView(generics.RetrieveUpdateAPIView): serializer_class = CurrentUserSerializer permission_classes = [IsAuthenticated] + def perform_update(self, serializer): + instance = serializer.save() + if self.request.POST.get("delete_profile_image", "").lower() == "true": + instance.profile_image.delete() + def get_object(self): return self.request.user From 24509ecdbe58df15369dd696cf07d719a0fec081 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Mon, 13 Jul 2026 12:22:07 -0400 Subject: [PATCH 04/14] Fix update avatar logic --- templates/v3/user_profile_edit.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/templates/v3/user_profile_edit.html b/templates/v3/user_profile_edit.html index 0eca82282..891739c41 100644 --- a/templates/v3/user_profile_edit.html +++ b/templates/v3/user_profile_edit.html @@ -105,8 +105,7 @@ {# Invisible input, toggles the edit box appearing #} - {# File input, shown in the empty state #} - {% include 'v3/includes/_field_file.html' with name=user_profile_form.avatar.name extra_class='user-profile__empty-avatar-input' accept='image/png,image/jpeg' only %} + {% include 'v3/includes/_field_file.html' with id="field-avatar-modal-new" name=user_profile_form.avatar.name extra_class='user-profile__empty-avatar-input' accept='image/png,image/jpeg' only %}