diff options
author | Matthew Lemon <y@yulqen.org> | 2024-12-03 17:10:23 +0000 |
---|---|---|
committer | Matthew Lemon <y@yulqen.org> | 2024-12-03 17:10:23 +0000 |
commit | 3bcd728b0bd37c95865205a75ffbddfb2e086f90 (patch) | |
tree | 181a79c8e8b156509bfe03c34fd8ceff57382b20 | |
parent | 38f7a88a15f1278df6a44242e2e22814914beddb (diff) |
First cut at email verification
10 files changed, 209 insertions, 81 deletions
diff --git a/alphabetlearning/payments/admin.py b/alphabetlearning/payments/admin.py index e9b70db..3980f67 100644 --- a/alphabetlearning/payments/admin.py +++ b/alphabetlearning/payments/admin.py @@ -1,14 +1,11 @@ from django.contrib import admin from .models import CartItem +from .models import EmailSignup +from .models import PendingEmailVerification from .models import Price -from .models import Product from .models import ShoppingCart from .models import Subscription -from .models import SubscriptionPlan -from .models import EmailSignup -from alphabetlearning.resources.models import Resource - # class PriceAdmin(admin.ModelAdmin): # list_display = @@ -43,7 +40,8 @@ class SubscriptionAdmin(admin.ModelAdmin): # admin.site.register(Product, ProductAdmin) admin.site.register(Price) +admin.site.register(PendingEmailVerification) admin.site.register(ShoppingCart, ShoppingCartAdmin) admin.site.register(CartItem) admin.site.register(Subscription, SubscriptionAdmin) -admin.site.register(EmailSignup, SuccessEmailSignupAdmin)
\ No newline at end of file +admin.site.register(EmailSignup, SuccessEmailSignupAdmin) diff --git a/alphabetlearning/payments/migrations/0010_pendingemailverification.py b/alphabetlearning/payments/migrations/0010_pendingemailverification.py new file mode 100644 index 0000000..c49a2cb --- /dev/null +++ b/alphabetlearning/payments/migrations/0010_pendingemailverification.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.4 on 2024-12-03 16:18 + +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('payments', '0009_emailsignup_alter_price_resource'), + ] + + operations = [ + migrations.CreateModel( + name='PendingEmailVerification', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('email', models.EmailField(max_length=254)), + ('first_name', models.CharField(max_length=100)), + ('last_name', models.CharField(max_length=100)), + ('verification_token', models.UUIDField(default=uuid.uuid4, editable=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('is_verified', models.BooleanField(default=False)), + ], + ), + ] diff --git a/alphabetlearning/payments/migrations/0011_remove_pendingemailverification_first_name_and_more.py b/alphabetlearning/payments/migrations/0011_remove_pendingemailverification_first_name_and_more.py new file mode 100644 index 0000000..e13f6d3 --- /dev/null +++ b/alphabetlearning/payments/migrations/0011_remove_pendingemailverification_first_name_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.4 on 2024-12-03 16:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('payments', '0010_pendingemailverification'), + ] + + operations = [ + migrations.RemoveField( + model_name='pendingemailverification', + name='first_name', + ), + migrations.RemoveField( + model_name='pendingemailverification', + name='last_name', + ), + ] diff --git a/alphabetlearning/payments/models.py b/alphabetlearning/payments/models.py index 7a8c7bb..0702427 100644 --- a/alphabetlearning/payments/models.py +++ b/alphabetlearning/payments/models.py @@ -1,3 +1,5 @@ +import uuid + from django.conf import settings from django.core.exceptions import ValidationError from django.db import models @@ -5,6 +7,13 @@ from django.db import models from alphabetlearning.resources.models import Resource +class PendingEmailVerification(models.Model): + email = models.EmailField() + verification_token = models.UUIDField(default=uuid.uuid4, editable=False) + created_at = models.DateTimeField(auto_now_add=True) + is_verified = models.BooleanField(default=False) + + class EmailSignup(models.Model): email = models.EmailField(unique=True) date_added = models.DateTimeField(auto_now_add=True) diff --git a/alphabetlearning/payments/urls.py b/alphabetlearning/payments/urls.py index 12f445d..38393e0 100644 --- a/alphabetlearning/payments/urls.py +++ b/alphabetlearning/payments/urls.py @@ -21,7 +21,9 @@ urlpatterns = [ path("landing/", views.ProductLandingPageView.as_view(), name="landing"), path("delete-cart-item/<int:pk>", views. DeleteCartItem.as_view(), name="delete_cart_item"), path("webhooks/stripe/", views.stripe_webhook, name="stripe-webhook"), - path("email_signup/", views.email_signup, name="email_signup"), + # path("email_signup/", views.email_signup, name="email_signup"), + path("email-sign-up-verification/", views.email_signup_verification, name="email_signup_verification"), + path("verify-email/<str:token>/", views.verify_email, name="verify_email"), path("success_email_signup/", views.SuccessEmailSignupView.as_view(), name="success_email_signup"), path('privacy-policy/', privacy_policy, name='privacy_policy'), # Add this line ] diff --git a/alphabetlearning/payments/views.py b/alphabetlearning/payments/views.py index d0172d0..9691a9f 100644 --- a/alphabetlearning/payments/views.py +++ b/alphabetlearning/payments/views.py @@ -15,6 +15,7 @@ from django.views.generic import DeleteView from django.views.generic import TemplateView from alphabetlearning.payments.models import EmailSignup +from alphabetlearning.payments.models import PendingEmailVerification from alphabetlearning.resources.models import Resource from alphabetlearning.users.models import User @@ -43,38 +44,78 @@ from .models import ShoppingCart stripe.api_key = settings.STRIPE_SECRET_KEY + class SuccessEmailSignupView(TemplateView): template_name = "payments/success_email_signup.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['email'] = self.request.POST.get('email') + context["email"] = self.request.POST.get("email") return context -def email_signup(request): - if request.method == 'POST': - email = request.POST.get('email') - if email: - EmailSignup.objects.get_or_create(email=email) - # Send email to user - send_mail( - "Thank you for signing up", - "You have successfully signed up for our newsletter.", - settings.DEFAULT_FROM_EMAIL, - [email], - fail_silently=False, - ) - # Send email to admin - admin_email = "admin@example.com" # Replace with actual admin email - send_mail( - "New Email Signup", - f"A new user has signed up with the email: {email}", - settings.DEFAULT_FROM_EMAIL, - [admin_email], - fail_silently=False, - ) - return redirect(reverse('payments:success_email_signup')) # Redirect to a success page or similar - return render(request, 'pages/home.html') # Adjust as necessary + +def email_signup_verification(request): + if request.method == "POST": + email = request.POST.get("email") + + # Create pending verification + pending_verification = PendingEmailVerification.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' + message = f''' + Hello!, + + You recently requested to sign up to the Alpabet Learning contract list. + + Please click the following link to verify your email address: + {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, + ) + 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 = PendingEmailVerification.objects.get( + verification_token=token, + is_verified=False + ) + + # Create the subscriber + EmailSignup.objects.create( + email=pending.email, + ) + + # Mark as verified + pending.is_verified = True + pending.save() + + return render(request, 'payments/verification_success.html') + + except PendingEmailVerification.DoesNotExist: + return render(request, 'payments/verification_failed.html') + def create_line_items(resources): price_objs = [r.price_obj.first() for r in resources] @@ -205,6 +246,7 @@ def stripe_webhook(request): ) return HttpResponse(status=200) + class DeleteCartItem(DeleteView): model = CartItem success_url = reverse_lazy("payments:cart_detail") @@ -219,5 +261,6 @@ class DeleteCartItem(DeleteView): request.user.shoppingcart.delete() return redirect("resources:resource_list") + def privacy_policy(request): - return render(request, 'pages/privacy_policy.html') + return render(request, "pages/privacy_policy.html") diff --git a/alphabetlearning/templates/pages/home.html b/alphabetlearning/templates/pages/home.html index d837dbb..bda011c 100644 --- a/alphabetlearning/templates/pages/home.html +++ b/alphabetlearning/templates/pages/home.html @@ -4,13 +4,13 @@ <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 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> - + <hr style=" border: none; @@ -29,22 +29,19 @@ 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> + <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 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' %}" + action="{% url 'payments:email_signup_verification' %}" class="mb-4" style="padding: 30px 0" > @@ -60,28 +57,38 @@ placeholder="Your email address..." /> </div> - <button type="submit" style="background-color: red; color: white; border: none; padding: 20px 50px; border-radius: 10px; font-size: 1.5rem; font-weight: bold;"> - Submit - </button> + <button + type="submit" + style=" + background-color: red; + color: white; + border: none; + padding: 20px 50px; + border-radius: 10px; + font-size: 1.5rem; + font-weight: bold; + " + > + 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" - >Privacy and Legal Notice</a - >.</small - > - </p> + <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" + >Privacy and Legal Notice</a + >.</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. + <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> @@ -129,31 +136,32 @@ </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 </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" + > + 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;"> - Resources will be available to purchase individually - without a subscription. - </p> - </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 @@ -177,8 +185,12 @@ /> </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> + <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 %} diff --git a/alphabetlearning/templates/payments/verification_failed.html b/alphabetlearning/templates/payments/verification_failed.html new file mode 100644 index 0000000..804320a --- /dev/null +++ b/alphabetlearning/templates/payments/verification_failed.html @@ -0,0 +1,4 @@ +{% extends "base.html" %} {% load static %} + +<h1>Verification Failed</h1> +<p>Sorry, this verification link is invalid or has already been used.</p> diff --git a/alphabetlearning/templates/payments/verification_sent.html b/alphabetlearning/templates/payments/verification_sent.html new file mode 100644 index 0000000..1de1236 --- /dev/null +++ b/alphabetlearning/templates/payments/verification_sent.html @@ -0,0 +1,6 @@ +{% extends "base.html" %} {% load static %} {$ block content $} + +<h1>Verification Email Sent</h1> +<p>Please check your email at {{ email }} to complete your subscription.</p> + +{$ endblock content $} diff --git a/alphabetlearning/templates/payments/verification_success.html b/alphabetlearning/templates/payments/verification_success.html new file mode 100644 index 0000000..1f8e95a --- /dev/null +++ b/alphabetlearning/templates/payments/verification_success.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} {% load static %} + +<h1>Email Verified Successfully</h1> +<p> + Thank you for verifying your email. You have been added to our subscriber + list. +</p> |