55from django .contrib import messages
66from django .contrib .auth import get_user_model
77from django .contrib .auth .decorators import login_required
8+ from django .http import Http404
89from django .shortcuts import redirect , render
10+ from django .urls import reverse
911
1012from ..auth .decorators import eighth_admin_required
1113from ..bus .models import Route
14+ from ..notifications .tasks import email_send_task
1215from ..users .models import Email
1316from .forms import BusRouteForm , DarkModeForm , EmailFormset , NotificationOptionsForm , PreferredPictureForm , PrivacyOptionsForm
17+ from .models import UnverifiedEmail
1418
1519# from .forms import (BusRouteForm, DarkModeForm, EmailFormset, NotificationOptionsForm, PhoneFormset, PreferredPictureForm, PrivacyOptionsForm,
1620# WebsiteFormset)
@@ -52,6 +56,20 @@ def get_personal_info(user):
5256 return personal_info , num_fields
5357
5458
59+ def send_verification_email (request , user , email ):
60+ email_link = UnverifiedEmail (user = user , email = email )
61+ email_link .save ()
62+
63+ verification_link = request .build_absolute_uri (reverse ("verify_email" , args = [email_link .verification_token ]))
64+ base_url = request .build_absolute_uri (reverse ("index" ))
65+ data = {"verification_link" : verification_link , "base_url" : base_url }
66+ headers = {"From" : "ION <no-reply@tjhsst.edu>" }
67+
68+ email_send_task .delay (
69+ "preferences/email/verify_email.txt" , "preferences/email/verify_email.html" , data , "ION Email Verification" , [email .address ], headers
70+ )
71+
72+
5573def save_personal_info (request , user ):
5674 # phone_formset = PhoneFormset(request.POST, instance=user, prefix="pf")
5775 phone_formset = None
@@ -68,7 +86,21 @@ def save_personal_info(request, user):
6886 # else:
6987 # errors.append("Could not set phone numbers.")
7088 if email_formset .is_valid ():
71- email_formset .save ()
89+ new_emails = email_formset .save (commit = False )
90+
91+ # Manually handle saving the formset so we can flag new emails as unverified.
92+ for email in new_emails :
93+ if email ._state .adding :
94+ email .verified = False
95+ email .save ()
96+ send_verification_email (request , user , email )
97+ messages .success (request , f"Successfully sent verification email to '{ email .address } '. The link will expire in 6 hours." )
98+
99+ for deleted_email in email_formset .deleted_objects :
100+ try :
101+ deleted_email .delete ()
102+ except deleted_email .DoesNotExist :
103+ pass
72104 else :
73105 for error in email_formset .errors :
74106 if isinstance (error .get ("address" ), list ):
@@ -207,6 +239,13 @@ def save_notification_options(request, user):
207239 if field in notification_options and notification_options [field ] == fields [field ]:
208240 pass
209241 else :
242+ # Users should only be able to set verified emails as their primary email.
243+ if field == "primary_email" and fields [field ] is not None :
244+ email = Email .objects .filter (user = user , address = fields [field ]).first ()
245+ if not email .verified :
246+ messages .error (request , "You may only set verified emails as your primary email." )
247+ continue
248+
210249 setattr (user , field , fields [field ])
211250 user .save ()
212251 try :
@@ -290,6 +329,27 @@ def save_dark_mode_settings(request, user):
290329 return dark_mode_form
291330
292331
332+ @login_required
333+ def verify_email_view (request , email_uuid ):
334+ """ "Verify the UUID associated with the unverified email."""
335+ user = request .user
336+
337+ unverified_email = UnverifiedEmail .objects .filter (verification_token = email_uuid , user = user ).first ()
338+
339+ # If the uuid isn't found or link is expired, it will return the default 404 form.
340+ if unverified_email is None or unverified_email .is_expired ():
341+ raise Http404
342+
343+ verified_mail = unverified_email .email
344+ verified_mail .verified = True
345+
346+ verified_mail .save ()
347+ unverified_email .delete ()
348+
349+ context = {"email_address" : verified_mail .address }
350+ return render (request , "preferences/verify_email.html" , context )
351+
352+
293353@login_required
294354def preferences_view (request ):
295355 """View and process updates to the preferences page."""
@@ -331,6 +391,13 @@ def preferences_view(request):
331391 email_formset = EmailFormset (instance = user , prefix = "ef" )
332392 # website_formset = WebsiteFormset(instance=user, prefix="wf")
333393
394+ # Flag emails as verified or unverified for templating.
395+ for form in email_formset :
396+ if form .instance .pk :
397+ form .verified = form .instance .verified
398+ else :
399+ form .verified = None
400+
334401 if user .is_student :
335402 preferred_pic = get_preferred_pic (user )
336403 bus_route = get_bus_route (user )
0 commit comments