From 5a191bd2c3245beccdeda63ce481139bfa204fb8 Mon Sep 17 00:00:00 2001 From: mhewel <58829786+mhewel@users.noreply.github.com> Date: Wed, 27 Nov 2024 21:47:38 +0100 Subject: [PATCH] If user is not logged in then send token by email to validate email --- members/apps.py | 27 +++++-- members/forms/volunteer_request_form.py | 14 +++- members/models/emailitem.py | 38 +++++---- members/models/emailtemplate.py | 6 +- members/models/volunteerrequest.py | 8 -- .../templates/members/volunteer_request.html | 57 ++++++++++++- members/urls.py | 7 +- members/views/VolunteerRequest.py | 81 ++++++++++++++++++- members/views/__init__.py | 2 +- 9 files changed, 201 insertions(+), 39 deletions(-) diff --git a/members/apps.py b/members/apps.py index ae902ff4..06860cf0 100644 --- a/members/apps.py +++ b/members/apps.py @@ -1,24 +1,39 @@ from django.apps import AppConfig from django.db.utils import OperationalError + class MembersConfig(AppConfig): - name = 'members' + name = "members" def ready(self): from members.models.emailtemplate import EmailTemplate - # Check if the NEW_VOLUNTEER email template exists, and create it if it does not + # Check if email templates exists, and create them if they do not try: if not EmailTemplate.objects.filter(idname="NEW_VOLUNTEER").exists(): EmailTemplate.objects.create( idname="NEW_VOLUNTEER", name="Ny frivillig", - description="Email template for nye frivillig forespørgsler", + description="Ny frivillig forespørgsel", from_address="kontakt@codingpirates.dk", subject="Ny frivillig til Coding Pirates {{ department }}", - body_html="

A new volunteer request has been made.

Details: {{ volunteer_request }}

", - body_text="A new volunteer request has been made.\nDetails: {{ volunteer_request }}", + body_html=""" +

Se detaljer i medlemssystemet, under Admin

""", + body_text=""" + Se detaljer i medlemssystemet, under Admin""", + ) + + if not EmailTemplate.objects.filter(idname="SECURITY_TOKEN").exists(): + EmailTemplate.objects.create( + idname="SECURITY_TOKEN", + name="Security Token", + description="Sikkerhedskode", + from_address="kontakt@codingpirates.dk", + subject="Sikkerhedskode for Coding Pirates", + body_html="

Din sikkerhedskode er: {{ token }}

", + body_text="Din sikkerhedskode er: {{ token }}", ) + except OperationalError: # Handle the case where the database is not ready yet - pass \ No newline at end of file + pass diff --git a/members/forms/volunteer_request_form.py b/members/forms/volunteer_request_form.py index 4c9cf14e..f3c0f50d 100644 --- a/members/forms/volunteer_request_form.py +++ b/members/forms/volunteer_request_form.py @@ -42,9 +42,9 @@ def render(self, name, value, attrs=None, choices=()): class VolunteerRequestForm(forms.ModelForm): department_list = forms.ModelMultipleChoiceField( queryset=Department.objects.filter(closed_dtm__isnull=True) - .order_by("id") + .order_by("name") .distinct(), - widget=CustomCheckboxSelectMultiple(), + widget=CheckboxSelectMultiple(), required=True, label="Vælg Afdeling(er)x", ) @@ -55,6 +55,13 @@ class VolunteerRequestForm(forms.ModelForm): label="Vælg person fra familien", ) + email_token = forms.CharField( + required=False, + label="Indtast den 6-cifrede kode sendt til din email", + max_length=6, + widget=forms.HiddenInput() # Initially hidden + ) + class Meta: model = VolunteerRequest fields = [ @@ -67,6 +74,7 @@ class Meta: "info_reference", "info_whishes", "department_list", + "email_token", ] def __init__(self, *args, **kwargs): @@ -87,6 +95,7 @@ def __init__(self, *args, **kwargs): self.fields["phone"].widget = forms.HiddenInput() self.fields["age"].widget = forms.HiddenInput() self.fields["zip"].widget = forms.HiddenInput() + self.fields["email_token"].widget = forms.HiddenInput() else: self.fields["family_member"].widget = forms.HiddenInput() @@ -106,6 +115,7 @@ def __init__(self, *args, **kwargs): Div(Field("info_reference"), css_class="col-md-12"), Div(Field("info_whishes"), css_class="col-md-12"), Div(Field("department_list"), css_class="col-md-12"), + Div(Field("email_token"), css_class="col-md-12", id="email-token-field"), css_class="row", ), ), diff --git a/members/models/emailitem.py b/members/models/emailitem.py index 8add9cd3..d7b95f2d 100644 --- a/members/models/emailitem.py +++ b/members/models/emailitem.py @@ -8,6 +8,7 @@ from django.utils.html import format_html from django.utils.html import escape from django.utils.safestring import mark_safe +from django.db import transaction import uuid @@ -122,23 +123,30 @@ def email_link(self): # send this email. Notice no checking of race condition, so this should be called by # cronscript and made sure the same mail is not sent multiple times in parallel def send(self): - self.sent_dtm = timezone.now() - self.save() - try: - send_mail( - self.subject, - self.body_text, - settings.SITE_CONTACT, - (self.receiver,), - html_message=self.body_html, - ) - except Exception as e: - self.send_error = str(type(e)) - self.send_error = self.send_error + str(e) + with transaction.atomic(): + # Lock the email item to avoid race conditions + email_item = EmailItem.objects.select_for_update().get(pk=self.pk) + if email_item.sent_dtm is not None: + # Email has already been sent, so skip sending it again + return + + self.sent_dtm = timezone.now() self.save() - raise e # forward exception to job control + try: + send_mail( + self.subject, + self.body_text, + settings.SITE_CONTACT, + (self.receiver,), + html_message=self.body_html, + ) + except Exception as e: + self.send_error = str(type(e)) + self.send_error = self.send_error + str(e) + self.save() + raise e # forward exception to job control - self.save() + self.save() def __str__(self): return str(self.receiver) + " '" + self.subject + "'" diff --git a/members/models/emailtemplate.py b/members/models/emailtemplate.py index 4bd78063..bec1c00a 100644 --- a/members/models/emailtemplate.py +++ b/members/models/emailtemplate.py @@ -56,9 +56,10 @@ def makeEmail(self, receivers, context, allow_multiple_emails=False): members.models.person.Person, members.models.family.Family, members.models.department.Department, + str, ): raise Exception( - "Receiver must be of type Person or Family not " + "Receiver must be of type Person, Family, Department or email address string not " + str(type(receiver)) ) @@ -66,6 +67,9 @@ def makeEmail(self, receivers, context, allow_multiple_emails=False): if type(receiver) is str: # check if family blacklisted. (TODO) destination_address = receiver + person = None + family = None + department = None elif type(receiver) is members.models.person.Person: # skip if family does not want email if receiver.family.dont_send_mails: diff --git a/members/models/volunteerrequest.py b/members/models/volunteerrequest.py index 8d20f487..8b7fd524 100644 --- a/members/models/volunteerrequest.py +++ b/members/models/volunteerrequest.py @@ -49,11 +49,3 @@ def __str__(self): else: return f"{self.person} ({self.created.strftime('%Y-%m-%d %H:%M:%S')})" - """ - def save(self, *args, **kwargs): - print("VolunteerRequest.Save()") - print(f" pk:{self.pk} person:{self.person} name:{self.name}") - print(f" email:{self.email}. phone:{self.phone}") - super().save(*args, **kwargs) - print(" saved") - """ diff --git a/members/templates/members/volunteer_request.html b/members/templates/members/volunteer_request.html index e5013654..59e46485 100644 --- a/members/templates/members/volunteer_request.html +++ b/members/templates/members/volunteer_request.html @@ -20,11 +20,64 @@

Ønsker du at være frivillig - og ikke allerede oprettet i medlemssystemet {% endfor %} {% endif %} -
+ {% csrf_token %} {{ volunteer_request_form|crispy }} - + {% if not user.is_authenticated %} + + + {% endif %} +
+ + {% if not user.is_authenticated %} + + {% endif %} {% endblock %} \ No newline at end of file diff --git a/members/urls.py b/members/urls.py index 2241e848..d90021d2 100644 --- a/members/urls.py +++ b/members/urls.py @@ -1,6 +1,8 @@ from django.urls import re_path from django.views.generic import TemplateView +from members.views import volunteer_request_view, generate_code + from members.views import ( AccountCreate, Activities, @@ -23,6 +25,7 @@ userCreated, volunteerSignup, ) + from django.contrib.auth import views as auth_views from graphene_django.views import GraphQLView from django.views.decorators.csrf import csrf_exempt @@ -77,7 +80,9 @@ re_path(r"^membership/$", Membership, name="membership"), re_path(r"^support_membership/$", SupportMembership, name="support_membership"), re_path(r"^volunteer$", volunteerSignup, name="volunteer_signup"), - re_path(r"^volunteer_request/$", volunteer_request_view, name="volunteer_request"), + re_path( + r"^volunteer_request/$", volunteer_request_view, name="volunteer_request"), + re_path(r"^generate_code/$", generate_code, name="generate_code"), re_path( r"^volunteer_request_created/$", TemplateView.as_view(template_name="members/volunteer_request_created.html"), diff --git a/members/views/VolunteerRequest.py b/members/views/VolunteerRequest.py index 80082b62..be90cc78 100644 --- a/members/views/VolunteerRequest.py +++ b/members/views/VolunteerRequest.py @@ -1,18 +1,82 @@ -from django.shortcuts import render -from django.http import HttpResponseRedirect +from django.shortcuts import render, redirect +from django.http import HttpResponseRedirect, JsonResponse from django.urls import reverse from django.contrib import messages from members.forms import VolunteerRequestForm from members.models.volunteerrequestdepartment import VolunteerRequestDepartment from members.models.volunteerrequest import VolunteerRequest from members.models.emailtemplate import EmailTemplate +import random +import json +def generate_code(request): + if request.method == "POST": + try: + data = json.loads(request.body) + email = data.get("email") + if email: + token = str(random.randint(100000, 999999)) + request.session["email_token"] = token + + email_template = EmailTemplate.objects.get(idname="SECURITY_TOKEN") + context = {"token": token} + email_items = email_template.makeEmail([email], context, True) + for email_item in email_items: + email_item.send() # Send the email immediately + + return JsonResponse({"success": True}) + else: + return JsonResponse({"success": False, "error": "Email is required."}) + except Exception as e: + return JsonResponse({"success": False, "error": str(e)}) + else: + return JsonResponse({"success": False, "error": "Invalid request method."}) def volunteer_request_view(request): if request.method == "POST": volunteer_request_form = VolunteerRequestForm(request.POST, user=request.user) if volunteer_request_form.is_valid(): + if not request.user.is_authenticated: + email = volunteer_request_form.cleaned_data["email"] + email_token = volunteer_request_form.cleaned_data["email_token"] + session_token = request.session.get("email_token") + + if not session_token: + # Generate and send the email token + token = str(random.randint(100000, 999999)) + request.session["email_token"] = token + + email_template = EmailTemplate.objects.get(idname="SECURITY_TOKEN") + context = {"token": token} + email_items = email_template.makeEmail([email], context, True) + for email_item in email_items: + email_item.send() # Send the email immediately + + messages.info( + request, + "En 6-cifret kode er sendt til din email. Indtast koden for at fortsætte.", + ) + return render( + request, + "members/volunteer_request.html", + { + "volunteer_request_form": volunteer_request_form, + }, + ) + + if email_token != session_token: + messages.error( + request, "Den indtastede kode er forkert. Prøv igen." + ) + return render( + request, + "members/volunteer_request.html", + { + "volunteer_request_form": volunteer_request_form, + }, + ) + family_member = volunteer_request_form.cleaned_data.get("family_member") if family_member: vol_req_obj = VolunteerRequest.objects.create( @@ -27,6 +91,7 @@ def volunteer_request_view(request): ], info_whishes=volunteer_request_form.cleaned_data["info_whishes"], ) + else: vol_req_obj = volunteer_request_form.save() @@ -42,12 +107,22 @@ def volunteer_request_view(request): "volunteer_request": vol_req_obj, "department": department, } - email_template.makeEmail(department, context) + email_items = email_template.makeEmail(department, context, True) + for email_item in email_items: + email_item.send() # Send the email immediately + # Clear the email_token from the session + if "email_token" in request.session: + del request.session["email_token"] messages.success( request, "Your volunteer request has been submitted successfully!" ) + + if request.user.is_authenticated: + messages.success(request, "Your volunteer request has been submitted successfully!") + return redirect("entry_page") + return HttpResponseRedirect(reverse("volunteer_request_created")) else: return render( diff --git a/members/views/__init__.py b/members/views/__init__.py index c64a6180..2ebd328f 100644 --- a/members/views/__init__.py +++ b/members/views/__init__.py @@ -19,4 +19,4 @@ from members.views.paymentGatewayErrorView import paymentGatewayErrorView from members.views.userCreated import userCreated from members.views.volunteerSignup import volunteerSignup -from members.views.VolunteerRequest import volunteer_request_view +from members.views.VolunteerRequest import volunteer_request_view, generate_code