Update emails
This commit is contained in:
parent
164a712902
commit
af5bee5147
18 changed files with 481 additions and 170 deletions
18
judge/migrations/0163_email_change.py
Normal file
18
judge/migrations/0163_email_change.py
Normal 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),
|
||||
),
|
||||
]
|
|
@ -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):
|
||||
|
|
20
judge/utils/email_render.py
Normal file
20
judge/utils/email_render.py
Normal 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
104
judge/views/email.py
Normal 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"),
|
||||
},
|
||||
)
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue