From 2c65f7ceb0b9778d65f9985ec885298fb2b3a278 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Thu, 4 Jun 2026 16:49:57 -0400 Subject: [PATCH 01/10] Continue work --- core/mixins.py | 6 ++- docker-compose.yml | 72 +++++++++++++++---------------- templates/v3/accounts/signup.html | 11 +++-- users/views.py | 19 +++++++- 4 files changed, 66 insertions(+), 42 deletions(-) diff --git a/core/mixins.py b/core/mixins.py index ae0f9b246..fe4ce9f3b 100644 --- a/core/mixins.py +++ b/core/mixins.py @@ -21,7 +21,11 @@ class V3Mixin: v3_template_name = None def dispatch(self, request, *args, **kwargs): - if self.v3_template_name and flag_is_active(request, "v3"): + if ( + self.v3_template_name + and flag_is_active(request, "v3") + and request.method == "GET" + ): self._v3_active = True return self.render_v3_response() self._v3_active = False diff --git a/docker-compose.yml b/docker-compose.yml index 3d294274d..8af215b21 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,43 +49,43 @@ services: - ../website2022/:/website stop_signal: SIGKILL - # mailman-core: - # image: maxking/mailman-core - # stop_grace_period: 5s - # ports: - # - "8001:8001" # API - # - "8024:8024" # LMTP - incoming emails - # volumes: - # - ./mailman/core:/opt/mailman/ - # networks: - # - backend - # env_file: - # - .env - # depends_on: - # - db + mailman-core: + image: maxking/mailman-core + stop_grace_period: 5s + ports: + - "8001:8001" # API + - "8024:8024" # LMTP - incoming emails + volumes: + - ./mailman/core:/opt/mailman/ + networks: + - backend + env_file: + - .env + depends_on: + - db - # mailman-web: - # image: maxking/mailman-web - # entrypoint: /opt/mailman-docker/compose-start.sh - # env_file: - # - .env - # environment: - # - "DOCKER_DIR=/opt/mailman-docker" - # - "PYTHON=python3" - # - "WEB_PORT=8008" - # depends_on: - # - redis - # - db - # stop_signal: SIGKILL - # ports: - # - "8008:8008" # HTTP - # - "8080:8080" # uwsgi - # volumes: - # - .:/code - # - ./mailman/web:/opt/mailman-web-data - # - ./docker:/opt/mailman-docker - # networks: - # - backend + mailman-web: + image: maxking/mailman-web + entrypoint: /opt/mailman-docker/compose-start.sh + env_file: + - .env + environment: + - "DOCKER_DIR=/opt/mailman-docker" + - "PYTHON=python3" + - "WEB_PORT=8008" + depends_on: + - redis + - db + stop_signal: SIGKILL + ports: + - "8008:8008" # HTTP + - "8080:8080" # uwsgi + volumes: + - .:/code + - ./mailman/web:/opt/mailman-web-data + - ./docker:/opt/mailman-docker + networks: + - backend celery-worker: build: diff --git a/templates/v3/accounts/signup.html b/templates/v3/accounts/signup.html index b174dd783..bf4379b77 100644 --- a/templates/v3/accounts/signup.html +++ b/templates/v3/accounts/signup.html @@ -18,15 +18,16 @@ redirect_field_value (string, optional, default unset) — URL to redirect to after signup; hidden input omitted when unset password_rules (list, optional, default unset) — rule objects for password validation checklist {% endcomment %} -{% load static %} +{% load static socialaccount %} {% block auth_content %}

Create an account

Advance your career, learn from experts, and help shape the future of Boost and C++.

Create an account

OR

- {% include "v3/includes/_button.html" with label="Continue with GitHub" url="#" icon_name="github" style="secondary" %} - {% include "v3/includes/_button.html" with label="Continue with Google" url="#" icon_name="google-colored" style="secondary" %} + {% provider_login_url "github" process="login" scope=scope auth_params=auth_params as github_oauth_url %} + {% provider_login_url "google" process="login" scope=scope auth_params=auth_params as google_oauth_url %} + {% include "v3/includes/_button.html" with label="Continue with GitHub" url=github_oauth_url icon_name="github" style="secondary" %} + {% include "v3/includes/_button.html" with label="Continue with Google" url=google_oauth_url icon_name="google-colored" style="secondary" %} {% include "v3/includes/_button.html" with label="Already have an account? Sign in →" url=login_url style="primary" %}
{% endblock auth_content %} diff --git a/users/views.py b/users/views.py index 3d03b8685..39744c392 100644 --- a/users/views.py +++ b/users/views.py @@ -547,7 +547,7 @@ def form_invalid(self, form): return res if res else super().form_invalid(form) -class CustomSignupView(ClaimExistingAccountMixin, SignupView): +class CustomSignupView(ClaimExistingAccountMixin, V3Mixin, SignupView): """ Override the allauth SignupView to customize behavior: @@ -556,6 +556,23 @@ class CustomSignupView(ClaimExistingAccountMixin, SignupView): with authors and maintainers. """ + v3_template_name = "v3/accounts/signup.html" + + def get_v3_context_data(self, **kwargs): + context = super().get_v3_context_data(**kwargs) + context["password_rules"] = build_password_rules() + context["page_title"] = getattr(self, "page_title", "Account") + context["foreground_image_url"] = large_static( + "img/v3/auth-page/auth-page-foreground.png" + ) + context["background_image_url"] = large_static( + "img/v3/auth-page/auth-page-background.png" + ) + context["login_url"] = reverse_lazy("v3-login") + context["signup_url"] = reverse_lazy("v3-signup") + context["password_reset_url"] = reverse_lazy("v3-password-reset") + return context + def form_invalid(self, form): """ Override this form to catch users who were created as part of the GitHub data From 63fd56311ecd111dcdf0c12f53efc96fccb3f8dd Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Fri, 5 Jun 2026 14:00:22 -0400 Subject: [PATCH 02/10] Clean up commits --- core/mixins.py | 29 +++++++++++++++++-- docker-compose.yml | 72 +++++++++++++++++++++++----------------------- users/views.py | 46 ++--------------------------- 3 files changed, 66 insertions(+), 81 deletions(-) diff --git a/core/mixins.py b/core/mixins.py index fe4ce9f3b..4dfa1c40b 100644 --- a/core/mixins.py +++ b/core/mixins.py @@ -1,7 +1,9 @@ -from django.http import Http404 -from django.urls import URLPattern, URLResolver, get_resolver +from django.http import Http404, HttpResponseNotFound +from django.urls import URLPattern, URLResolver, get_resolver, reverse_lazy from waffle import flag_is_active +from core.templatetags.custom_static import large_static + class V3Mixin: """Renders a v3 template when the 'v3' waffle flag is active. @@ -67,3 +69,26 @@ def walk(patterns): yield entry, view_class yield from walk(get_resolver().url_patterns) + + +class V3AuthContextMixin(V3Mixin): + """Shared context for all V3 auth pages (signup, login, password reset, etc.).""" + + def dispatch(self, request, *args, **kwargs): + if not flag_is_active(request, "v3"): + return HttpResponseNotFound() + return super().dispatch(request, *args, **kwargs) + + def get_v3_context_data(self, **kwargs): + context = super().get_v3_context_data(**kwargs) + context["page_title"] = getattr(self, "page_title", "Account") + context["foreground_image_url"] = large_static( + "img/v3/auth-page/auth-page-foreground.png" + ) + context["background_image_url"] = large_static( + "img/v3/auth-page/auth-page-background.png" + ) + context["login_url"] = reverse_lazy("v3-login") + context["signup_url"] = reverse_lazy("v3-signup") + context["password_reset_url"] = reverse_lazy("v3-password-reset") + return context diff --git a/docker-compose.yml b/docker-compose.yml index 8af215b21..3d294274d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,43 +49,43 @@ services: - ../website2022/:/website stop_signal: SIGKILL - mailman-core: - image: maxking/mailman-core - stop_grace_period: 5s - ports: - - "8001:8001" # API - - "8024:8024" # LMTP - incoming emails - volumes: - - ./mailman/core:/opt/mailman/ - networks: - - backend - env_file: - - .env - depends_on: - - db + # mailman-core: + # image: maxking/mailman-core + # stop_grace_period: 5s + # ports: + # - "8001:8001" # API + # - "8024:8024" # LMTP - incoming emails + # volumes: + # - ./mailman/core:/opt/mailman/ + # networks: + # - backend + # env_file: + # - .env + # depends_on: + # - db - mailman-web: - image: maxking/mailman-web - entrypoint: /opt/mailman-docker/compose-start.sh - env_file: - - .env - environment: - - "DOCKER_DIR=/opt/mailman-docker" - - "PYTHON=python3" - - "WEB_PORT=8008" - depends_on: - - redis - - db - stop_signal: SIGKILL - ports: - - "8008:8008" # HTTP - - "8080:8080" # uwsgi - volumes: - - .:/code - - ./mailman/web:/opt/mailman-web-data - - ./docker:/opt/mailman-docker - networks: - - backend + # mailman-web: + # image: maxking/mailman-web + # entrypoint: /opt/mailman-docker/compose-start.sh + # env_file: + # - .env + # environment: + # - "DOCKER_DIR=/opt/mailman-docker" + # - "PYTHON=python3" + # - "WEB_PORT=8008" + # depends_on: + # - redis + # - db + # stop_signal: SIGKILL + # ports: + # - "8008:8008" # HTTP + # - "8080:8080" # uwsgi + # volumes: + # - .:/code + # - ./mailman/web:/opt/mailman-web-data + # - ./docker:/opt/mailman-docker + # networks: + # - backend celery-worker: build: diff --git a/users/views.py b/users/views.py index 39744c392..b5e06edff 100644 --- a/users/views.py +++ b/users/views.py @@ -6,7 +6,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib import auth from django.contrib.messages.views import SuccessMessageMixin -from django.http import HttpResponseNotFound, HttpResponseRedirect +from django.http import HttpResponseRedirect from django.urls import reverse_lazy from django.views.generic import DetailView, FormView from django.views.generic.base import TemplateView @@ -22,11 +22,9 @@ from rest_framework import generics from rest_framework import viewsets from rest_framework.permissions import IsAuthenticated, AllowAny -from waffle import flag_is_active from core.constants import BadgeToken -from core.mixins import V3Mixin -from core.templatetags.custom_static import large_static +from core.mixins import V3Mixin, V3AuthContextMixin from libraries.models import CommitAuthorEmail from .forms import ( PreferencesForm, @@ -547,7 +545,7 @@ def form_invalid(self, form): return res if res else super().form_invalid(form) -class CustomSignupView(ClaimExistingAccountMixin, V3Mixin, SignupView): +class CustomSignupView(ClaimExistingAccountMixin, V3AuthContextMixin, SignupView): """ Override the allauth SignupView to customize behavior: @@ -558,21 +556,6 @@ class CustomSignupView(ClaimExistingAccountMixin, V3Mixin, SignupView): v3_template_name = "v3/accounts/signup.html" - def get_v3_context_data(self, **kwargs): - context = super().get_v3_context_data(**kwargs) - context["password_rules"] = build_password_rules() - context["page_title"] = getattr(self, "page_title", "Account") - context["foreground_image_url"] = large_static( - "img/v3/auth-page/auth-page-foreground.png" - ) - context["background_image_url"] = large_static( - "img/v3/auth-page/auth-page-background.png" - ) - context["login_url"] = reverse_lazy("v3-login") - context["signup_url"] = reverse_lazy("v3-signup") - context["password_reset_url"] = reverse_lazy("v3-password-reset") - return context - def form_invalid(self, form): """ Override this form to catch users who were created as part of the GitHub data @@ -600,29 +583,6 @@ def get_context_data(self, **kwargs): return context -class V3AuthContextMixin(V3Mixin): - """Shared context for all V3 auth pages (signup, login, password reset, etc.).""" - - def dispatch(self, request, *args, **kwargs): - if not flag_is_active(request, "v3"): - return HttpResponseNotFound() - return super().dispatch(request, *args, **kwargs) - - def get_v3_context_data(self, **kwargs): - context = super().get_v3_context_data(**kwargs) - context["page_title"] = getattr(self, "page_title", "Account") - context["foreground_image_url"] = large_static( - "img/v3/auth-page/auth-page-foreground.png" - ) - context["background_image_url"] = large_static( - "img/v3/auth-page/auth-page-background.png" - ) - context["login_url"] = reverse_lazy("v3-login") - context["signup_url"] = reverse_lazy("v3-signup") - context["password_reset_url"] = reverse_lazy("v3-password-reset") - return context - - class V3SignupView(V3AuthContextMixin, TemplateView): v3_template_name = "v3/accounts/signup.html" page_title = "Create An Account" From ef5268afe4ab9c6508e63bd0a70a1663f7bc6aa8 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Fri, 5 Jun 2026 14:16:34 -0400 Subject: [PATCH 03/10] Update oauth to only show available providers --- templates/v3/accounts/signup.html | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/templates/v3/accounts/signup.html b/templates/v3/accounts/signup.html index bf4379b77..dbb93810b 100644 --- a/templates/v3/accounts/signup.html +++ b/templates/v3/accounts/signup.html @@ -63,10 +63,17 @@

Create an account

OR

{% endblock auth_content %} From 4c57ebc6111f7e261f2a953d518f41ec66dc7af9 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Fri, 5 Jun 2026 14:24:14 -0400 Subject: [PATCH 04/10] Remove V3 specific sign up view --- config/v3_urls.py | 6 ------ users/views.py | 15 +++++---------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/config/v3_urls.py b/config/v3_urls.py index 545b93adc..7dceef146 100644 --- a/config/v3_urls.py +++ b/config/v3_urls.py @@ -55,7 +55,6 @@ V3PasswordResetFromKeyDoneView, V3PasswordResetFromKeyView, V3PasswordResetView, - V3SignupView, ) v3_urlpatterns = [ @@ -74,11 +73,6 @@ V3AllTypesCreateView.as_view(), name="v3-news-create", ), - path( - "v3/accounts/signup/", - V3SignupView.as_view(), - name="v3-signup", - ), path( "v3/accounts/login/", V3LoginView.as_view(), diff --git a/users/views.py b/users/views.py index b5e06edff..eb1ad133c 100644 --- a/users/views.py +++ b/users/views.py @@ -556,6 +556,11 @@ class CustomSignupView(ClaimExistingAccountMixin, V3AuthContextMixin, SignupView v3_template_name = "v3/accounts/signup.html" + def get_v3_context_data(self, **kwargs): + context = super().get_v3_context_data(**kwargs) + context["password_rules"] = build_password_rules() + return context + def form_invalid(self, form): """ Override this form to catch users who were created as part of the GitHub data @@ -583,16 +588,6 @@ def get_context_data(self, **kwargs): return context -class V3SignupView(V3AuthContextMixin, TemplateView): - v3_template_name = "v3/accounts/signup.html" - page_title = "Create An Account" - - def get_v3_context_data(self, **kwargs): - context = super().get_v3_context_data(**kwargs) - context["password_rules"] = build_password_rules() - return context - - class V3LoginView(V3AuthContextMixin, TemplateView): v3_template_name = "v3/accounts/login.html" page_title = "Login" From aa2b1190c60373acaae7063c63986ff892c1b783 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Tue, 9 Jun 2026 17:24:57 -0400 Subject: [PATCH 05/10] Add disabled style to buttons if TOU not accepted --- static/css/v3/auth-page.css | 31 ++++++++++++++++++++-- static/css/v3/forms.css | 4 +++ templates/v3/accounts/signup.html | 43 +++++++++++++++++++++---------- users/forms.py | 5 ++++ users/views.py | 8 ++++++ 5 files changed, 76 insertions(+), 15 deletions(-) diff --git a/static/css/v3/auth-page.css b/static/css/v3/auth-page.css index fce53cccc..36ac9e00b 100644 --- a/static/css/v3/auth-page.css +++ b/static/css/v3/auth-page.css @@ -70,7 +70,8 @@ body:has(.auth-page) .header { } .auth-page__illustration-foreground { - margin-top: 80px; /* This is the size of the header navbar, added so that the content will not be hidden behind it */ + margin-top: 80px; + /* This is the size of the header navbar, added so that the content will not be hidden behind it */ position: absolute; inset: 0; width: 100%; @@ -85,7 +86,8 @@ body:has(.auth-page) .header { justify-content: center; align-items: center; padding: var(--space-xlarge); - margin-top: 80px; /* This is the size of the header navbar, added so that the content will not be hidden behind it */ + margin-top: 80px; + /* This is the size of the header navbar, added so that the content will not be hidden behind it */ } .auth-page__content-inner { @@ -175,6 +177,31 @@ body:has(.auth-page) .header { background: var(--color-surface-weak); } +/* ── Social Card Disable pending tou ────────────────────── */ +.auth-page__signup-form:not(:has(input[name="accept_terms_of_use"]:checked)) a.btn-secondary, +.auth-page__signup-form:not(:has(input[name="accept_terms_of_use"]:checked)) button.btn { + opacity: 0.5; + pointer-events: none; + cursor: default; +} + +.auth-page__signup-form:not(:has(input[name="accept_terms_of_use"]:checked)):has(input[name="checkbox-GitHub"]:checked) input[name="accept_terms_of_use"] { + background-color: var(--color-surface-error-weak, #fdf2f2); + border-color: var(--color-stroke-error, #d53f3f33); +} + +.auth-page__invisible-check { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + /* ── Responsive (Tablet) ────────────────────────────── */ @media (max-width: 1279px) { .auth-page__wrapper { diff --git a/static/css/v3/forms.css b/static/css/v3/forms.css index abe4fdbeb..53c95c8ca 100644 --- a/static/css/v3/forms.css +++ b/static/css/v3/forms.css @@ -641,6 +641,10 @@ color: var(--color-text-primary, #050816); } +.checkbox__label a { + text-decoration: underline; +} + .checkbox--disabled { opacity: 0.5; cursor: not-allowed; diff --git a/templates/v3/accounts/signup.html b/templates/v3/accounts/signup.html index dbb93810b..561d40c5f 100644 --- a/templates/v3/accounts/signup.html +++ b/templates/v3/accounts/signup.html @@ -24,7 +24,7 @@

Create an account

Advance your career, learn from experts, and help shape the future of Boost and C++.

-
Create an account {% for error in form.non_field_errors %}{% endfor %} {% endif %} {% include "v3/includes/_field_checkbox.html" with name="mailing_list" label="Also join the Boost Developers Mailing List" %} + {% if redirect_field_value %} {% endif %} {% include "v3/includes/_button.html" with label="Create Account" type="submit" style="primary" alpine_disabled="hasErrors" %} -
-

OR

- + {% endblock auth_content %} diff --git a/users/forms.py b/users/forms.py index 4e1d0df8b..385c41050 100644 --- a/users/forms.py +++ b/users/forms.py @@ -5,6 +5,7 @@ from django import forms from allauth.account.forms import ResetPasswordKeyForm +from allauth.account.forms import SignupForm from .models import Preferences from news.models import NEWS_MODELS @@ -25,6 +26,10 @@ def save(self, **kwargs): return result +class CustomSignUpForm(SignupForm): + accept_terms_of_use = forms.BooleanField(required=True) + + class PreferencesForm(forms.ModelForm): allow_notification_own_news_approved = forms.MultipleChoiceField( choices=NEWS_ENTRY_CHOICES, diff --git a/users/views.py b/users/views.py index eb1ad133c..0eb1beefe 100644 --- a/users/views.py +++ b/users/views.py @@ -23,6 +23,8 @@ from rest_framework import viewsets from rest_framework.permissions import IsAuthenticated, AllowAny +from waffle import flag_is_active + from core.constants import BadgeToken from core.mixins import V3Mixin, V3AuthContextMixin from libraries.models import CommitAuthorEmail @@ -31,6 +33,7 @@ UserProfileForm, UserProfilePhotoForm, DeleteAccountForm, + CustomSignUpForm, ) from .models import User from .password_rules import build_password_rules @@ -556,6 +559,11 @@ class CustomSignupView(ClaimExistingAccountMixin, V3AuthContextMixin, SignupView v3_template_name = "v3/accounts/signup.html" + def get_form_class(self): + if flag_is_active(self.request, "v3"): + return CustomSignUpForm + return super().get_form_class() + def get_v3_context_data(self, **kwargs): context = super().get_v3_context_data(**kwargs) context["password_rules"] = build_password_rules() From 29dcc1b680b20a4be7d60feef309b0ab14fd232f Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Wed, 10 Jun 2026 15:09:12 -0400 Subject: [PATCH 06/10] Add css styling for error states --- static/css/v3/auth-page.css | 26 +++++++++++++------------- templates/v3/accounts/signup.html | 12 +++++++----- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/static/css/v3/auth-page.css b/static/css/v3/auth-page.css index 36ac9e00b..fc45432de 100644 --- a/static/css/v3/auth-page.css +++ b/static/css/v3/auth-page.css @@ -185,21 +185,21 @@ body:has(.auth-page) .header { cursor: default; } -.auth-page__signup-form:not(:has(input[name="accept_terms_of_use"]:checked)):has(input[name="checkbox-GitHub"]:checked) input[name="accept_terms_of_use"] { - background-color: var(--color-surface-error-weak, #fdf2f2); - border-color: var(--color-stroke-error, #d53f3f33); +.auth-page__signup-form:has(input[name="invisible-check-protect"]:checked):not(:has(input[name="accept_terms_of_use"]:checked)) .auth-page_tou-checkbox .checkbox__box { + background-color: var(--color-surface-error-weak); + border-color: var(--color-stroke-error); } -.auth-page__invisible-check { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; +.auth-page__signup-form:has(input[name="invisible-check-protect"]:checked):not(:has(input[name="accept_terms_of_use"]:checked)) .auth-page_tou-checkbox .checkbox__label { + color: var(--color-text-error); +} + +.auth-page__signup-form:has(input[name="invisible-check-protect"]:checked):not(:has(input[name="accept_terms_of_use"]:checked)) .auth-page__invisible-check-label { + pointer-events: none; +} + +.auth-page__invisible-check-label .btn { + width: 100%; } /* ── Responsive (Tablet) ────────────────────────────── */ diff --git a/templates/v3/accounts/signup.html b/templates/v3/accounts/signup.html index 561d40c5f..b5c8308be 100644 --- a/templates/v3/accounts/signup.html +++ b/templates/v3/accounts/signup.html @@ -54,7 +54,7 @@

Create an account

{% for error in form.non_field_errors %}{% endfor %} {% endif %} {% include "v3/includes/_field_checkbox.html" with name="mailing_list" label="Also join the Boost Developers Mailing List" %} -