diff options
author | Matthew Lemon <y@yulqen.org> | 2024-12-05 17:42:08 +0000 |
---|---|---|
committer | Matthew Lemon <y@yulqen.org> | 2024-12-05 17:42:08 +0000 |
commit | 30101453a75aaa635e3458d509dec164a6a68ac1 (patch) | |
tree | ceb332bad155a7abe037acc9c123a0bf42dd9ec7 | |
parent | 7cd2fd2b75bc5597e9b2a128bf61201910be6df2 (diff) |
Add unique constraint to EmailVerification email field
This update enhances the EmailVerification model by ensuring the email field is unique, preventing duplicate records. A new form, EmailVerificationForm, was also introduced to handle email cleaning and validation, which enhances user input handling. Additionally, existing views and templates have been updated to integrate this form, improving the user experience and error feedback.
-rw-r--r-- | alphabetlearning/payments/forms.py | 14 | ||||
-rw-r--r-- | alphabetlearning/payments/migrations/0013_alter_emailverification_email.py | 18 | ||||
-rw-r--r-- | alphabetlearning/payments/models.py | 3 | ||||
-rw-r--r-- | alphabetlearning/payments/views.py | 147 | ||||
-rw-r--r-- | alphabetlearning/templates/pages/home.html | 282 |
5 files changed, 267 insertions, 197 deletions
diff --git a/alphabetlearning/payments/forms.py b/alphabetlearning/payments/forms.py new file mode 100644 index 0000000..f69397f --- /dev/null +++ b/alphabetlearning/payments/forms.py @@ -0,0 +1,14 @@ +from django import forms +from .models import EmailVerification + + +class EmailVerificationForm(forms.ModelForm): + class Meta: + model = EmailVerification + fields = ['email'] + + def clean_email(self): + email = self.cleaned_data.get('email') + if EmailVerification.objects.filter(email=email).exists(): + raise forms.ValidationError("This email address is already in use.") + return email
\ No newline at end of file diff --git a/alphabetlearning/payments/migrations/0013_alter_emailverification_email.py b/alphabetlearning/payments/migrations/0013_alter_emailverification_email.py new file mode 100644 index 0000000..5d537bb --- /dev/null +++ b/alphabetlearning/payments/migrations/0013_alter_emailverification_email.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-12-05 16:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('payments', '0012_rename_pendingemailverification_emailverification'), + ] + + operations = [ + migrations.AlterField( + model_name='emailverification', + name='email', + field=models.EmailField(max_length=254, unique=True), + ), + ] diff --git a/alphabetlearning/payments/models.py b/alphabetlearning/payments/models.py index 9ea9de2..3a3ba10 100644 --- a/alphabetlearning/payments/models.py +++ b/alphabetlearning/payments/models.py @@ -10,7 +10,7 @@ from alphabetlearning.resources.models import Resource class EmailVerification(models.Model): - email = models.EmailField() + email = models.EmailField(unique=True) verification_token = models.UUIDField(default=uuid.uuid4, editable=False) created_at = models.DateTimeField(auto_now_add=True) is_verified = models.BooleanField(default=False) @@ -23,7 +23,6 @@ class EmailVerification(models.Model): def __str__(self): return f"Email verification for {self.email}" - class EmailSignup(models.Model): email = models.EmailField(unique=True) date_added = models.DateTimeField(auto_now_add=True) diff --git a/alphabetlearning/payments/views.py b/alphabetlearning/payments/views.py index 4b6f79f..dda7900 100644 --- a/alphabetlearning/payments/views.py +++ b/alphabetlearning/payments/views.py @@ -18,7 +18,7 @@ from alphabetlearning.payments.models import EmailSignup from alphabetlearning.payments.models import EmailVerification from alphabetlearning.resources.models import Resource from alphabetlearning.users.models import User - +from .forms import EmailVerificationForm from .models import CartItem from .models import Price from .models import ShoppingCart @@ -56,70 +56,101 @@ class SuccessEmailSignupView(TemplateView): def email_signup_verification(request): if request.method == "POST": - email = request.POST.get("email") - - # Create pending verification - pending_verification = EmailVerification.objects.create( - email=email, - ) - - # Generate verification URL - verification_url = request.build_absolute_uri( - reverse('payments:verify_email', args=[str(pending_verification.verification_token)]) - ) - - # Send verification email - subject = 'Alphabet Learning - Email Verification' - html_message = f''' - <html> - <body> - <img src="http://localhost:8000/static/images/AL_long_logo_black_grey.png" alt="Alphabet Learning Logo" style="max-width: 200px; margin-bottom: 20px;"> - <p>Hi!</p> - <p>You recently requested to sign up to the Alphabet Learning contact list.</p> - <p>Please click the following link to verify your email address within 24 hours:</p> - <p><a href="{verification_url}">{verification_url}</a></p> - <p>If you didn't request this, please ignore this email.</p> - <p>Best regards,</p> - <p>The Alphabet Learning Team</p> - </body> - </html> + form = EmailVerificationForm(request.POST) + if form.is_valid(): + # Create pending verification + pending_verification = EmailVerification.objects.create( + email=form.cleaned_data.get("email"), + ) + # Generate verification URL + verification_url:str = request.build_absolute_uri( + reverse('payments:verify_email', args=[str(pending_verification.verification_token)]) + ) + email = process_verification_emails(verification_url, form.cleaned_data.get("email"), pending_verification, request) + return render(request, 'payments/verification_sent.html', { + 'email': email + }) + else: + email = process_verification_emails(request.POST.get("email"), warn=True) + return render(request, 'payments/verification_sent.html', { + 'email': email + }) + else: + form = EmailVerificationForm() + return render(request, "pages/home.html", {"form": form}) # Adjust as necessary + + +def process_verification_emails(email: str, verification_url: str=False, email_verification_obj: EmailVerification=False, warn=False): + + if warn is False: + html_warning_message = f''' + <p>Please click the following link to verify your email address within 24 hours:</p> + <p><a href="{verification_url}">{verification_url}</a></p> + <p>If you didn't request this, please ignore this email.</p> ''' - message = f''' - Hi!, - - You recently requested to sign up to the Alphabet Learning contact list. - + warning_message = f''' Please click the following link to verify your email address within 24 hours: {verification_url} If you didn't request this, please ignore this email. - - Best regards, - The Alphabet Learning Team ''' - send_mail( - subject, - message, - settings.DEFAULT_FROM_EMAIL, - [email], - fail_silently=False, - html_message=html_message - ) - admin_message = f''' - Joanna/Matthew, - - {email} has just signed up to the Alphabet Learning contact list. They are awaiting verification. - - I will email again if they follow through with the verification. - - Best regards, - The Alphabet Learning Server + admin_warn = "They are not already subscribed." + else: + html_warning_message = f''' + <p>You are already subscribed to our list - no further action is required.</p> ''' + warning_message = ''' + You are already subscribed to our list - no further action is required. + ''' + admin_warn = "They have already subscribed so have been told of this fact in their verification email. No further action required." + + # Send verification email + subject = 'Alphabet Learning - Email Verification' + html_message = f''' + <html> + <body> + <img src="http://localhost:8000/static/images/AL_long_logo_black_grey.png" alt="Alphabet Learning Logo" style="max-width: 200px; margin-bottom: 20px;"> + <p>Hi!</p> + <p>You recently requested to sign up to the Alphabet Learning contact list.</p> + {html_warning_message} + <p>Best regards,</p> + <p>The Alphabet Learning Team</p> + </body> + </html> + ''' + message = f''' + Hi!, + + You recently requested to sign up to the Alphabet Learning contact list. + + {warning_message} + + Best regards, + The Alphabet Learning Team + ''' + send_mail( + subject, + message, + settings.DEFAULT_FROM_EMAIL, + [email], + fail_silently=False, + html_message=html_message + ) + admin_message = f''' + Joanna/Matthew, + + {email} has just signed up to the Alphabet Learning contact list. They are awaiting verification. + + {admin_warn} + + I will email again if they follow through with the verification. + + Best regards, + The Alphabet Learning Server + ''' + mail_admins(subject, admin_message, fail_silently=False) + return email + - mail_admins(subject, admin_message, fail_silently=False) - return render(request, 'payments/verification_sent.html', { - 'email': email - }) - return render(request, "pages/home.html") # Adjust as necessary def verify_email(request, token): try: pending = EmailVerification.objects.get( diff --git a/alphabetlearning/templates/pages/home.html b/alphabetlearning/templates/pages/home.html index bda011c..e0827c1 100644 --- a/alphabetlearning/templates/pages/home.html +++ b/alphabetlearning/templates/pages/home.html @@ -1,18 +1,18 @@ {% extends "base.html" %} {% load static %} {% block content %} -<div class="container text-center"> - <div class="row"> - <div class="px-4 py-5 mt-5 mb-0"> - <h1 class="display-1 fw-bold">Alphabet Learning</h1> - <p class="display-6 mb-4 mt-4 p-4 rounded" style="color: #919191"> - High quality educational resources focused on simple concepts - and the first steps in learning. - </p> + <div class="container text-center"> + <div class="row"> + <div class="px-4 py-5 mt-5 mb-0"> + <h1 class="display-1 fw-bold">Alphabet Learning</h1> + <p class="display-6 mb-4 mt-4 p-4 rounded" style="color: #919191"> + High quality educational resources focused on simple concepts + and the first steps in learning. + </p> + </div> </div> - </div> - <hr - style=" + <hr + style=" border: none; height: 10px; background: linear-gradient( @@ -28,38 +28,46 @@ width: 100%; margin: 20px 0; " - /> + /> - <div class="row justify-content-center my-5"> - <div class="col-lg-12"> - <h2 class="display-4 font-weight-bolder my-3">Sign up</h2> - <div> - <p class="display-6 mb-4" style="color: darkgray"> - To be informed of the site launch and receive FREE resource - credits, join our new customer mailing list... - </p> - </div> - <form - method="post" - action="{% url 'payments:email_signup_verification' %}" - class="mb-4" - style="padding: 30px 0" - > - {% csrf_token %} - <div class="my-5"> - <div class="mb-4 text-start"> - <input - type="email" - class="sign-up-form-input" - id="email" - name="email" - required - placeholder="Your email address..." - /> - </div> - <button - type="submit" - style=" + <div class="row justify-content-center my-5"> + <div class="col-lg-12"> + <h2 class="display-4 font-weight-bolder my-3">Sign up</h2> + <div> + <p class="display-6 mb-4" style="color: darkgray"> + To be informed of the site launch and receive FREE resource + credits, join our new customer mailing list... + </p> + </div> + <form + method="post" + action="{% url 'payments:email_signup_verification' %}" + class="mb-4" + style="padding: 30px 0" + > + {% csrf_token %} + {{ form.non_field_errors }} + <div class="my-5"> + {% if form.email.errors %} + <div class="text-start my-2 border border-danger text-danger rounded"> + <p> + {{ form.email.errors }} + </p> + </div> + {% endif %} + <div class="mb-4 text-start"> + <input + type="email" + class="sign-up-form-input" + id="email" + name="email" + required + placeholder="Your email address..." + /> + </div> + <button + type="submit" + style=" background-color: red; color: white; border: none; @@ -68,33 +76,33 @@ font-size: 1.5rem; font-weight: bold; " - > - Submit - </button> - </div> - <p class="text-muted text-center mt-3"> - <small + > + Submit + </button> + </div> + <p class="text-muted text-center mt-3"> + <small >By joining our list, you agree to our - <a - href="{% url 'payments:privacy_policy' %}" - class="text-decoration-none" + <a + href="{% url 'payments:privacy_policy' %}" + class="text-decoration-none" >Privacy and Legal Notice</a - >.</small - > + >.</small + > + </p> + </form> + </div> + <div class="col-lg-12"> + <h2 class="display-4 font-weight-bolder my-3">Bonus offer!</h2> + <p class="display-6 mb-4" style="color: darkgray"> + As a thank-you, the first fifty customers who join our mailing + list will receive a 50% discount once the site is live. </p> - </form> - </div> - <div class="col-lg-12"> - <h2 class="display-4 font-weight-bolder my-3">Bonus offer!</h2> - <p class="display-6 mb-4" style="color: darkgray"> - As a thank-you, the first fifty customers who join our mailing - list will receive a 50% discount once the site is live. - </p> + </div> </div> - </div> - <hr - style=" + <hr + style=" border: none; height: 10px; background: linear-gradient( @@ -110,87 +118,87 @@ width: 100%; margin: 20px 0; " - /> + /> - <div class="row justify-content-center"> - <div class="col-md-4 mb-5 mb-md-0"> - <img - src="{% static 'images/funnel4.png' %}" - class="img-fluid rotated-image" - alt="Funnel 1" - /> - </div> - <div class="col-md-4 mb-5 mb-md-0"> - <img - src="{% static 'images/funnel5.png' %}" - class="img-fluid rotated-image" - alt="Funnel 2" - /> - </div> - <div class="col-md-4 mb-5 mb-md-0"> - <img - src="{% static 'images/funnel6.png' %}" - class="img-fluid rotated-image" - alt="Funnel 3" - /> + <div class="row justify-content-center"> + <div class="col-md-4 mb-5 mb-md-0"> + <img + src="{% static 'images/funnel4.png' %}" + class="img-fluid rotated-image" + alt="Funnel 1" + /> + </div> + <div class="col-md-4 mb-5 mb-md-0"> + <img + src="{% static 'images/funnel5.png' %}" + class="img-fluid rotated-image" + alt="Funnel 2" + /> + </div> + <div class="col-md-4 mb-5 mb-md-0"> + <img + src="{% static 'images/funnel6.png' %}" + class="img-fluid rotated-image" + alt="Funnel 3" + /> + </div> </div> - </div> - <div class="row my-5 d-flex align-items-stretch"> - <div class="col-md-12 d-flex"> - <div class="p-4 flex-fill"> - <h2 class="display-4 font-weight-bolder my-3"> - Learning from A to Z + <div class="row my-5 d-flex align-items-stretch"> + <div class="col-md-12 d-flex"> + <div class="p-4 flex-fill"> + <h2 class="display-4 font-weight-bolder my-3"> + Learning from A to Z + </h2> + <p + class="display-6" + style="margin-bottom: 20px; color: darkgray" + > + Focused on early concepts, these resources are designed to + be accessible to all learners. + </p> + </div> + </div> + <div class="display-4 font-weight-bolder my-5"> + <h2 class="display-4 font-weight-bolder mb-2"> + No commitment required </h2> - <p - class="display-6" - style="margin-bottom: 20px; color: darkgray" - > - Focused on early concepts, these resources are designed to - be accessible to all learners. + <p class="display-6" style="margin-bottom: 20px; color: darkgray"> + Resources will be available to purchase individually without a + subscription. </p> </div> </div> - <div class="display-4 font-weight-bolder my-5"> - <h2 class="display-4 font-weight-bolder mb-2"> - No commitment required - </h2> - <p class="display-6" style="margin-bottom: 20px; color: darkgray"> - Resources will be available to purchase individually without a - subscription. - </p> - </div> - </div> - <div class="row justify-content-center my-5"> - <div class="col-md-4 mb-5 mb-md-0"> - <img - src="{% static 'images/funnel1.png' %}" - class="img-fluid rotated-image" - alt="Funnel 1" - /> - </div> - <div class="col-md-4 mb-5 mb-md-0"> - <img - src="{% static 'images/funnel2.png' %}" - class="img-fluid rotated-image" - alt="Funnel 2" - /> - </div> - <div class="col-md-4 mb-5 mb-md-0"> - <img - src="{% static 'images/funnel3.png' %}" - class="img-fluid rotated-image" - alt="Funnel 3" - /> + <div class="row justify-content-center my-5"> + <div class="col-md-4 mb-5 mb-md-0"> + <img + src="{% static 'images/funnel1.png' %}" + class="img-fluid rotated-image" + alt="Funnel 1" + /> + </div> + <div class="col-md-4 mb-5 mb-md-0"> + <img + src="{% static 'images/funnel2.png' %}" + class="img-fluid rotated-image" + alt="Funnel 2" + /> + </div> + <div class="col-md-4 mb-5 mb-md-0"> + <img + src="{% static 'images/funnel3.png' %}" + class="img-fluid rotated-image" + alt="Funnel 3" + /> + </div> </div> + <p + class="display-6 mb-4 mt-4 p-4 col-12 rounded text-center" + style="color: darkgray" + > + Alphabet Learning is currently in active <br/>development and will be + launched in Spring 2025. + </p> </div> - <p - class="display-6 mb-4 mt-4 p-4 col-12 rounded text-center" - style="color: darkgray" - > - Alphabet Learning is currently in active <br />development and will be - launched in Spring 2025. - </p> -</div> {% endblock content %} |