Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SS-643 Users can edit their account details #235

Merged
merged 30 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d321a3f
Updated info about using Serve for teaching (#194)
akochari Apr 19, 2024
2b5cadc
Revert "Updated info about using Serve for teaching (#194)"
akochari Apr 19, 2024
2d769be
Merge branch 'staging' into main
sandstromviktor May 23, 2024
ab7d54f
Merge branch 'staging'
churnikov Aug 14, 2024
46f589c
Merge branch 'staging'
churnikov Sep 19, 2024
c958069
SS-643-Make-an-option-to-edit-account-details
anondo1969 Oct 7, 2024
7c8099b
SS-643-Make-an-option-to-edit-account-details
anondo1969 Oct 7, 2024
4e3de76
SS-643 extending ProfileForm
anondo1969 Oct 8, 2024
d38f4ce
SS-643 Updated edit HTML form
anondo1969 Oct 10, 2024
d86a443
SS-643 Updated edit HTML form
anondo1969 Oct 10, 2024
32b49eb
SS-643 pre-commit check fixed
anondo1969 Oct 10, 2024
bba6f8b
SS-643 pre-commit with black check
anondo1969 Oct 10, 2024
0048d66
SS-643 pre-commit with black check 2
anondo1969 Oct 10, 2024
cf33450
Merge branch 'develop' into SS-643-Make-an-option-to-edit-account-det…
anondo1969 Oct 14, 2024
cded668
fix failing e2e test
akochari Oct 14, 2024
8194013
change the edit profile icon
akochari Oct 14, 2024
a05bf15
uncomment the line I accidentally commented out
akochari Oct 14, 2024
ee23c4e
Update common/views.py
anondo1969 Oct 15, 2024
b3481fd
Update common/forms.py
anondo1969 Oct 15, 2024
6bb12aa
Update common/forms.py
anondo1969 Oct 15, 2024
41b2862
fix super class init.
anondo1969 Oct 15, 2024
491b6f8
changes in view
anondo1969 Oct 15, 2024
da00974
ensure login required in form post method
anondo1969 Oct 15, 2024
f78edf6
Merge branch 'develop' into SS-643-Make-an-option-to-edit-account-det…
anondo1969 Oct 15, 2024
7f13f1e
ensuring curl injection does not work
anondo1969 Oct 17, 2024
a96fdfd
fixing the profile-edit bug in superuser mode
anondo1969 Oct 21, 2024
5a7fa52
Merge branch 'develop' into SS-643-Make-an-option-to-edit-account-det…
anondo1969 Oct 21, 2024
0d50532
Merge branch 'develop' into SS-643-Make-an-option-to-edit-account-det…
anondo1969 Oct 21, 2024
2d11a65
differentiate admin user and common user with or without Staff/Superu…
anondo1969 Oct 22, 2024
ce5e4a8
differentiate admin user and common user with or without Staff/Superu…
anondo1969 Oct 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 14 additions & 77 deletions common/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
from django.core.validators import EmailValidator
from django.db import transaction
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _

from common.models import EmailVerificationTable, UserProfile
from studio.utils import get_logger

from django.utils.translation import gettext_lazy as _

logger = get_logger(__name__)


Expand Down Expand Up @@ -115,7 +114,7 @@ class UserForm(BootstrapErrorFormMixin, UserCreationForm):
"Swedish university</a> email address. If you are not affiliated with a Swedish university, "
"your account request will be reviewed manually."
),
#disabled = True,
# disabled = True,
anondo1969 marked this conversation as resolved.
Show resolved Hide resolved
)
password1 = forms.CharField(
min_length=8,
Expand Down Expand Up @@ -326,13 +325,14 @@ class Meta:
]


# creating a new form because UserForm is a UserCreationForm, which means 'exclude' in Meta or change in
anondo1969 marked this conversation as resolved.
Show resolved Hide resolved
# initialization won't work
class UserEditForm(BootstrapErrorFormMixin, forms.ModelForm):
first_name = forms.CharField(
min_length=1,
max_length=30,
label="First name",
widget=forms.TextInput(attrs={"class": "form-control"}),

)
last_name = forms.CharField(
min_length=1,
Expand All @@ -342,14 +342,11 @@ class UserEditForm(BootstrapErrorFormMixin, forms.ModelForm):
)
email = forms.EmailField(
max_length=254,
label="Email",
label="Email address",
widget=forms.TextInput(attrs={"class": "form-control"}),
help_text=mark_safe(
"Email address can not be changed. Please email [email protected] with any questions."
),
disabled = True,
help_text=mark_safe("Email address can not be changed. Please email [email protected] with any questions."),
disabled=True,
)


required_css_class = "required"

Expand All @@ -371,78 +368,18 @@ class Meta:

def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.data})"

'''
class ProfileEditForm(BootstrapErrorFormMixin, forms.ModelForm):
affiliation = forms.ChoiceField(
widget=forms.Select(attrs={"class": "form-control"}),
label="University",
choices=UNIVERSITIES,
help_text="Affiliation can not be changed. Please email [email protected] with any questions.",
disabled = True,
)
department = forms.CharField(
widget=ListTextWidget(data_list=DEPARTMENTS, name="department-list", attrs={"class": "form-control"}),
label="Department",
required=False,
help_text="Select closest department name or enter your own.",
)

required_css_class = "required"

class Meta:
model = UserProfile
fields = [
"affiliation",
"department",
"note",
"why_account_needed",
]

class ProfileEditForm(ProfileForm):
class Meta(ProfileForm.Meta):
exclude = [
"note",
"why_account_needed",
]

def __repr__(self):
return f"{self.__class__.__name__}({self.data})"
'''

class ProfileEditForm(ProfileForm):


class Meta(ProfileForm.Meta):

exclude = ["note",
"why_account_needed",]

def __init__(self, *args, **kwargs):
super(ProfileEditForm, self).__init__(*args, **kwargs)
anondo1969 marked this conversation as resolved.
Show resolved Hide resolved
self.fields["affiliation"].disabled=True
#self.fields["affiliation"].required = False
self.fields["affiliation"].help_text="Affiliation can not be changed. Please email [email protected] with any questions."

'''
class UserEditForm(UserForm):


class Meta(UserForm.Meta):

exclude = [
"username",
"password1",
"password2",
]
def __init__(self, *args, **kwargs):
super(UserEditForm, self).__init__(*args, **kwargs)
self.fields["email"].disabled=True
self.fields["email"].help_text="Email address can not be changed. Please email [email protected] with any questions."
del self.fields["password1"]
del self.fields["password2"]
self.fields["email"].required = False
'''






self.fields["affiliation"].disabled = True
self.fields[
"affiliation"
].help_text = "Affiliation can not be changed. Please email [email protected] with any questions."
149 changes: 84 additions & 65 deletions common/views.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import authenticate, login, logout
from django.core.exceptions import ObjectDoesNotExist
from django.core.mail import send_mail
from django.db import transaction
from django.http.response import HttpResponseRedirect
from django.shortcuts import redirect, render, HttpResponse
from django.shortcuts import HttpResponse, redirect, render
from django.urls import reverse_lazy
from django.views.generic import CreateView, TemplateView
from django.views.generic.edit import UpdateView
from django.core.exceptions import ObjectDoesNotExist

from studio.utils import get_logger

from .forms import ProfileForm, SignUpForm, TokenVerificationForm, UserForm, ProfileEditForm, UserEditForm
from .forms import (
ProfileEditForm,
ProfileForm,
SignUpForm,
TokenVerificationForm,
UserEditForm,
UserForm,
)
from .models import EmailVerificationTable, UserProfile

logger = get_logger(__name__)


# Create your views here.
class HomeView(TemplateView):
template_name = "common/landing.html"
Expand Down Expand Up @@ -133,84 +141,95 @@ def post(self, request, *args, **kwargs):
messages.error(request, "Invalid token!")
return redirect("portal:home")
return render(request, self.template_name, {"form": form})


class EditProfileView(TemplateView):
"""

"""
template_name = "registration/edit_profile.html"

""" """
anondo1969 marked this conversation as resolved.
Show resolved Hide resolved

template_name = "user/profile_edit_form.html"

profile_edit_form_class = ProfileEditForm
user_edit_form_class= UserEditForm
user_edit_form_class = UserEditForm

def get_user_profile_info(self, request):
# Get the user profile
# Get the user profile from database
try:
# Note that not all users have a user profile object
# such as the admin superuser
user_profile_data = UserProfile.objects.get(user_id=request.user.id)
# Note that not all users have a user profile object
# such as the admin superuser
user_profile = UserProfile.objects.get(user_id=request.user.id)
except ObjectDoesNotExist as e:
logger.error(str(e), exc_info=True)
user_profile = UserProfile()
except Exception as e:
logger.error(str(e), exc_info=True)
user_profile = UserProfile()

return user_profile_data



return user_profile

def get(self, request, *args, **kwargs):

user_profile_data = self.get_user_profile_info(request)
profile_edit_form = self.profile_edit_form_class(initial={
"affiliation" : user_profile_data.affiliation,
"department" : user_profile_data.department
})


user_edit_form = self.user_edit_form_class(initial={
"email" : user_profile_data.user.email,
"first_name" : user_profile_data.user.first_name,
"last_name" : user_profile_data.user.last_name
})

profile_edit_form = self.profile_edit_form_class(
initial={"affiliation": user_profile_data.affiliation, "department": user_profile_data.department}
)

user_edit_form = self.user_edit_form_class(
initial={
"email": user_profile_data.user.email,
"first_name": user_profile_data.user.first_name,
"last_name": user_profile_data.user.last_name,
}
)

return render(request, self.template_name, {"form": user_edit_form, "profile_form": profile_edit_form})

def post(self, request, *args, **kwargs):

user_profile_data = self.get_user_profile_info(request)

user_form_details = self.user_edit_form_class(request.POST, instance=request.user, initial={
"email" : user_profile_data.user.email,})

profile_form_details = self.profile_edit_form_class(request.POST, instance=user_profile_data, initial={
"affiliation" : user_profile_data.affiliation,})


user_form_details = self.user_edit_form_class(
request.POST,
instance=request.user,
initial={
"email": user_profile_data.user.email,
},
)

profile_form_details = self.profile_edit_form_class(
request.POST,
instance=user_profile_data,
initial={
"affiliation": user_profile_data.affiliation,
},
)
Comment on lines +199 to +213
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure, that user cannot pass any other information in the form data.

For instance, that user via direct curl request cannot change their email or password.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used decorator to ensure login-required in 'common/urls.py' and in 'EditProfileView' class in 'common/views.py'.


if user_form_details.is_valid() and profile_form_details.is_valid():

user_form_details.save()
profile_form_details.save()


#logger.info(user_form_details.cleaned_data, exc_info=True)

logger.info("Updated First Name: "+str(self.get_user_profile_info(request).user.first_name), exc_info=True)
logger.info("Updated Last Name: "+str(self.get_user_profile_info(request).user.last_name), exc_info=True)
logger.info("Updated Department: "+str(self.get_user_profile_info(request).department), exc_info=True)

return render(request, "registration/edit_profile_done.html")

try:
with transaction.atomic():
user_form_details.save()
profile_form_details.save()

logger.info(
"Updated First Name: " + str(self.get_user_profile_info(request).user.first_name), exc_info=True
)
logger.info(
"Updated Last Name: " + str(self.get_user_profile_info(request).user.last_name), exc_info=True
)
logger.info(
"Updated Department: " + str(self.get_user_profile_info(request).department), exc_info=True
)

except Exception as e:
return HttpResponse("Error updating records: " + str(e))

return render(request, "user/profile.html", {"user_profile": self.get_user_profile_info(request)})

else:

# Redirect back to the same page if the data
# was invalid

#print (form.errors)
if user_form_details.is_valid()==False:
if not user_form_details.is_valid():
logger.error("Edit user error: " + str(user_form_details.errors), exc_info=True)
if profile_form_details.is_valid()==False:

if not profile_form_details.is_valid():
logger.error("Edit profile error: " + str(profile_form_details.errors), exc_info=True)

return render(request, self.template_name, {"form": user_form_details, "profile_form": profile_form_details})

return render(
request, self.template_name, {"form": user_form_details, "profile_form": profile_form_details}
)
2 changes: 1 addition & 1 deletion cypress/e2e/ui-tests/test-brute-force-login-attempts.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe("Test brute force login attempts are blocked", () => {

// Sign out before logging in again
cy.logf("Sign out before logging in again", Cypress.currentTest)
cy.get('button.btn-profile').click()
cy.get('button.btn-profile').contains("Profile").click()
cy.get('li.btn-group').find('button').contains("Sign out").click()
cy.get("title").should("have.text", "Logout | SciLifeLab Serve (beta)")

Expand Down
2 changes: 1 addition & 1 deletion templates/common/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{% url 'user-profile' %}"><i class="bi bi-person me-1"></i>My profile</a></li>
<li><a class="dropdown-item" href="{% url 'common:edit-profile' %}"><i class="bi bi-person me-1"></i>Edit profile</a></li>
<li><a class="dropdown-item" href="{% url 'common:edit-profile' %}"><i class="bi bi-pencil-square me-1"></i>Edit profile</a></li>
<li><a class="dropdown-item" href="{% url 'password_change' %}"><i class="bi bi-key me-1"></i>Change password</a></li>
<form action="{% url 'logout' %}" method="post">
{% csrf_token %}
Expand Down
Loading
Loading