Update emails

This commit is contained in:
cuom1999 2023-08-25 15:36:38 -05:00
parent 164a712902
commit af5bee5147
18 changed files with 481 additions and 170 deletions

View file

@ -0,0 +1,18 @@
# Generated by Django 3.2.18 on 2023-08-25 00:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("judge", "0162_profile_image"),
]
operations = [
migrations.AddField(
model_name="profile",
name="email_change_pending",
field=models.EmailField(blank=True, max_length=254, null=True),
),
]

View file

@ -237,6 +237,7 @@ class Profile(models.Model):
help_text=_("Notes for administrators regarding this user."),
)
profile_image = models.ImageField(upload_to=profile_image_path, null=True)
email_change_pending = models.EmailField(blank=True, null=True)
@cached_property
def organization(self):

View file

@ -0,0 +1,20 @@
from django.template.loader import render_to_string
from django.contrib.sites.shortcuts import get_current_site
from django.conf import settings
def render_email_message(request, contexts):
current_site = get_current_site(request)
email_contexts = {
"username": request.user.username,
"domain": current_site.domain,
"protocol": "https" if request.is_secure() else "http",
"site_name": settings.SITE_NAME,
"message": None,
"title": None,
"button_text": "Click here",
"url_path": None,
}
email_contexts.update(contexts)
message = render_to_string("general_email.html", email_contexts)
return message

104
judge/views/email.py Normal file
View file

@ -0,0 +1,104 @@
from django.contrib.auth.tokens import default_token_generator
from django.core.mail import send_mail
from django.shortcuts import render, redirect
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes, force_text
from django.conf import settings
from django import forms
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from urllib.parse import urlencode, urlunparse, urlparse
from judge.models import Profile
from judge.utils.email_render import render_email_message
class EmailChangeForm(forms.Form):
new_email = forms.EmailField(label=_("New Email"))
def clean_new_email(self):
new_email = self.cleaned_data.get("new_email")
if User.objects.filter(email=new_email).exists():
raise forms.ValidationError(_("An account with this email already exists."))
return new_email
@login_required
def email_change_view(request):
form = EmailChangeForm(request.POST or None)
if request.method == "POST" and form.is_valid():
new_email = request.POST.get("new_email")
user = request.user
profile = request.profile
# Generate a token for email verification
token = default_token_generator.make_token(user)
uid = urlsafe_base64_encode(force_bytes(user.pk))
# Send the email to the user
subject = _(f"{settings.SITE_NAME} - Email Change Request")
email_contexts = {
"message": _(
"We have received a request to change your email to this email. Click the button below to change your email:"
),
"title": _("Email Change"),
"button_text": _("Change Email"),
"url_path": reverse(
"email_change_verify", kwargs={"uidb64": uid, "token": token}
),
}
message = render_email_message(request, email_contexts)
send_mail(subject, message, settings.EMAIL_HOST_USER, [new_email])
profile.email_change_pending = new_email
profile.save()
return redirect("email_change_pending")
return render(
request,
"email_change/email_change.html",
{
"form": form,
"title": _("Change email"),
},
)
def verify_email_view(request, uidb64, token):
try:
uid = force_text(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
if user is not None and default_token_generator.check_token(user, token):
# Update the user's email address
profile = Profile.objects.get(user=user)
new_email = profile.email_change_pending
if new_email:
user.email = new_email
profile.email_change_pending = None
user.save()
profile.save()
return render(
request,
"email_change/email_change_success.html",
{"title": _("Success"), "user": user},
)
return render(
request, "email_change/email_change_failure.html", {"title": _("Invalid")}
)
def email_change_pending_view(request):
return render(
request,
"email_change/email_change_pending.html",
{
"title": _("Email change pending"),
},
)

View file

@ -15,7 +15,7 @@ from registration.backends.default.views import (
from registration.forms import RegistrationForm
from sortedm2m.forms import SortedMultipleChoiceField
from judge.models import Language, Organization, Profile, TIMEZONE
from judge.models import Language, Profile, TIMEZONE
from judge.utils.recaptcha import ReCaptchaField, ReCaptchaWidget
from judge.widgets import Select2MultipleWidget, Select2Widget
@ -43,29 +43,10 @@ class CustomRegistrationForm(RegistrationForm):
empty_label=None,
widget=Select2Widget(attrs={"style": "width:100%"}),
)
organizations = SortedMultipleChoiceField(
queryset=Organization.objects.filter(is_open=True),
label=_("Groups"),
required=False,
widget=Select2MultipleWidget(attrs={"style": "width:100%"}),
)
if ReCaptchaField is not None:
captcha = ReCaptchaField(widget=ReCaptchaWidget())
def clean_organizations(self):
organizations = self.cleaned_data.get("organizations") or []
max_orgs = settings.DMOJ_USER_MAX_ORGANIZATION_COUNT
if sum(org.is_open for org in organizations) > max_orgs:
raise forms.ValidationError(
_("You may not be part of more than {count} public groups.").format(
count=max_orgs
)
)
return self.cleaned_data["organizations"]
def clean_email(self):
if User.objects.filter(email=self.cleaned_data["email"]).exists():
raise forms.ValidationError(
@ -116,7 +97,6 @@ class RegistrationView(OldRegistrationView):
cleaned_data = form.cleaned_data
profile.timezone = cleaned_data["timezone"]
profile.language = cleaned_data["language"]
profile.organizations.add(*cleaned_data["organizations"])
profile.save()
return user