diff --git a/config/urls.py b/config/urls.py
index 634741cc..e4ebb856 100755
--- a/config/urls.py
+++ b/config/urls.py
@@ -52,6 +52,7 @@ from support.views import ContactView, SupportView
from users.views import (
CurrentUserAPIView,
CurrentUserProfileView,
+ NewCurrentUserProfileView,
ProfilePhotoGitHubUpdateView,
ProfilePhotoUploadView,
ProfilePreferencesView,
@@ -85,6 +86,11 @@ urlpatterns = (
ProfilePreferencesView.as_view(),
name="profile-preferences",
),
+ path(
+ "users/me/new/",
+ NewCurrentUserProfileView.as_view(),
+ name="profile-account-new",
+ ),
path("users/me/", CurrentUserProfileView.as_view(), name="profile-account"),
# Temp route to prove antora header
path("testheader/", antora_header_view, name="antora-header"),
diff --git a/templates/users/profile_base_new.html b/templates/users/profile_base_new.html
new file mode 100644
index 00000000..847198a5
--- /dev/null
+++ b/templates/users/profile_base_new.html
@@ -0,0 +1,21 @@
+{% extends "base.html" %}
+
+{% load static %}
+
+{% block subnav %}
+
+
+ {% if user.image %}
+

+ {% else %}
+
+ {% endif %}
+
+
+ {{ user.get_full_name }}
+ Joined {{ user.date_joined }}
+
+
+
+
+{% endblock %}
diff --git a/templates/users/profile_new.html b/templates/users/profile_new.html
new file mode 100644
index 00000000..17a93133
--- /dev/null
+++ b/templates/users/profile_new.html
@@ -0,0 +1,61 @@
+{% extends "users/profile_base_new.html" %}
+
+{% load static i18n %}
+
+{% block content %}
+
+
+
+
+

+
+
+
{% trans "Set Password" %}
+
+
+
+
{% trans "Update Profile Photo" %}
+
+ {% if user.github_username %}
+
+ {% endif %}
+
+
+
+
+
{% trans "Update Preferences" %}
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/users/tests/test_views.py b/users/tests/test_views.py
index 8f7c674b..cb62f718 100644
--- a/users/tests/test_views.py
+++ b/users/tests/test_views.py
@@ -1,6 +1,12 @@
import pytest
+import tempfile
-from ..forms import PreferencesForm
+from django.core.files.uploadedfile import SimpleUploadedFile
+
+from allauth.account.forms import ChangePasswordForm
+from PIL import Image
+
+from ..forms import PreferencesForm, UserProfilePhotoForm
from ..models import Preferences
@@ -57,3 +63,122 @@ def test_preferences_post_clears_options(
assert_messages(
response, [("success", "Your preferences were successfully updated.")]
)
+
+
+@pytest.mark.django_db
+def test_new_current_user_profile_not_authenticated(tp, user):
+ tp.assertLoginRequired("profile-account-new")
+
+
+@pytest.mark.django_db
+def test_new_current_user_profile_view_get(user, tp):
+ with tp.login(user):
+ response = tp.assertGoodView(tp.reverse("profile-account-new"), verbose=True)
+ assert isinstance(response.context["change_password_form"], ChangePasswordForm)
+ assert isinstance(response.context["profile_photo_form"], UserProfilePhotoForm)
+ assert isinstance(response.context["profile_preferences_form"], PreferencesForm)
+
+
+@pytest.mark.django_db
+def test_new_current_user_profile_view_post_valid_password(user, tp):
+ with tp.login(user):
+ response = tp.post(
+ tp.reverse("profile-account-new"),
+ data={
+ "email": user.email,
+ "oldpassword": "password",
+ "password1": "new_password",
+ "password2": "new_password",
+ },
+ follow=True,
+ )
+ assert response.status_code == 200
+ user.refresh_from_db()
+ user.check_password("new_password")
+
+
+@pytest.mark.django_db
+def test_new_current_user_profile_view_post_invalid_password(user, tp):
+ old_password = "password"
+ with tp.login(user):
+ response = tp.post(
+ tp.reverse("profile-account-new"),
+ data={
+ "email": user.email,
+ "oldpassword": "not the right password",
+ "password1": "new_password",
+ "password2": "new_password",
+ },
+ follow=True,
+ )
+ assert response.status_code == 200
+ user.refresh_from_db()
+ user.check_password(old_password)
+
+
+@pytest.mark.django_db
+def test_new_current_user_profile_view_post_valid_photo(user, tp):
+ """Test that a user can upload a new profile picture."""
+ # Create a temporary image file for testing
+ with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as temp_image:
+ image = Image.new("RGB", (200, 200))
+ image.save(temp_image, "jpeg")
+ temp_image.seek(0)
+
+ # Wrap temp_image in Django's File object
+ uploaded_file = SimpleUploadedFile(
+ name="test_image.jpg", content=temp_image.read(), content_type="image/jpeg"
+ )
+
+ with tp.login(user):
+ response = tp.post(
+ tp.reverse("profile-account-new"),
+ data={
+ "image": uploaded_file,
+ },
+ follow=True,
+ )
+ assert response.status_code == 200
+ user.refresh_from_db()
+ assert user.image
+ # confirm that password was not changed, as these are on the same screen
+ assert user.check_password("password")
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize("user_type", ["user", "moderator_user"])
+@pytest.mark.parametrize("form_field", PreferencesForm.Meta.fields)
+def test_new_current_user_profile_view_post_valid_preferences(
+ user_type, form_field, tp, request, assert_messages
+):
+ user = request.getfixturevalue(user_type)
+ original_preferences = {
+ field: getattr(user.preferences, field) for field in PreferencesForm.Meta.fields
+ }
+ new_preferences = {
+ field: [] for field in PreferencesForm.Meta.fields
+ } # Clear all options
+
+ with tp.login(user):
+ response = tp.post(
+ tp.reverse("profile-account-new"),
+ data={**new_preferences, "update_preferences": "Update Preeferences"},
+ follow=True,
+ )
+
+ assert response.status_code == 200
+ user.refresh_from_db()
+ for field in PreferencesForm.Meta.fields:
+ if (
+ field == "allow_notification_others_news_needs_moderation"
+ and user_type != "moderator_user"
+ ):
+ assert (
+ getattr(user.preferences, field) == original_preferences[field]
+ ) # Field should be unchanged for non-moderators
+ else:
+ assert getattr(user.preferences, field) == [] # Field should be cleared
+
+ assert_messages(
+ response, [("success", "Your preferences were successfully updated.")]
+ )
diff --git a/users/views.py b/users/views.py
index c29a9c76..23dd5f0b 100644
--- a/users/views.py
+++ b/users/views.py
@@ -5,6 +5,9 @@ from django.http import HttpResponseRedirect
from django.urls import reverse_lazy
from django.views.generic import DetailView, UpdateView
from django.views.generic.edit import FormView
+from django.views.generic.base import TemplateView
+
+from allauth.account.forms import ChangePasswordForm
from rest_framework import generics
from rest_framework import viewsets
@@ -123,3 +126,79 @@ class ProfilePreferencesView(LoginRequiredMixin, SuccessMessageMixin, UpdateView
def get_object(self):
return self.request.user.preferences
+
+
+class NewCurrentUserProfileView(LoginRequiredMixin, SuccessMessageMixin, TemplateView):
+ template_name = "users/profile_new.html"
+ success_message = "Your profile was successfully updated."
+ success_url = reverse_lazy("profile-account-new")
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context["change_password_form"] = ChangePasswordForm(user=self.request.user)
+ context["profile_photo_form"] = UserProfilePhotoForm(instance=self.request.user)
+ context["profile_preferences_form"] = PreferencesForm(
+ instance=self.request.user.preferences
+ )
+ return context
+
+ def post(self, request, *args, **kwargs):
+ """
+ Process each form submission individually if present
+ """
+ if "change_password" in request.POST:
+ change_password_form = ChangePasswordForm(
+ data=request.POST, user=self.request.user
+ )
+ self.change_password(change_password_form, request)
+
+ if "update_photo" in request.POST:
+ profile_photo_form = UserProfilePhotoForm(
+ self.request.POST, self.request.FILES, instance=self.request.user
+ )
+ self.update_photo(profile_photo_form, request)
+
+ if "update_github_photo" in request.POST:
+ self.update_github_photo(request)
+
+ if "update_preferences" in request.POST:
+ profile_preferences_form = PreferencesForm(
+ self.request.POST, instance=request.user.preferences
+ )
+ self.update_preferences(profile_preferences_form, request)
+
+ return HttpResponseRedirect(self.success_url)
+
+ def change_password(self, form, request):
+ """Change the password of the user."""
+ if form.is_valid():
+ self.object = request.user
+ self.object.set_password(form.cleaned_data["password1"])
+ self.object.save()
+ messages.success(request, "Your password was successfully updated.")
+ else:
+ for error in form.errors.values():
+ messages.error(request, f"{error}")
+
+ def update_photo(self, form, request):
+ """Update the profile photo of the user."""
+ if form.is_valid():
+ form.save()
+ messages.success(request, "Your profile photo was successfully updated.")
+ else:
+ for error in form.errors.values():
+ messages.error(request, f"{error}")
+
+ def update_github_photo(self, request):
+ """Update the GitHub photo of the user."""
+ tasks.update_user_github_photo(str(request.user.pk))
+ messages.success(request, "Your GitHub photo has been retrieved.")
+
+ def update_preferences(self, form, request):
+ """Update the preferences of the user."""
+ if form.is_valid():
+ form.save()
+ messages.success(request, "Your preferences were successfully updated.")
+ else:
+ for error in form.errors.values():
+ messages.error(request, f"{error}")