Skip to content

Commit

Permalink
If user is not logged in then send token by email to validate email
Browse files Browse the repository at this point in the history
  • Loading branch information
mhewel committed Nov 27, 2024
1 parent 11e703d commit 5a191bd
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 39 deletions.
27 changes: 21 additions & 6 deletions members/apps.py
Original file line number Diff line number Diff line change
@@ -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="[email protected]",
subject="Ny frivillig til Coding Pirates {{ department }}",
body_html="<p>A new volunteer request has been made.</p><p>Details: {{ volunteer_request }}</p>",
body_text="A new volunteer request has been made.\nDetails: {{ volunteer_request }}",
body_html="""
<p>Se detaljer i medlemssystemet, under Admin</p>""",
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="[email protected]",
subject="Sikkerhedskode for Coding Pirates",
body_html="<p>Din sikkerhedskode er: {{ token }}</p>",
body_text="Din sikkerhedskode er: {{ token }}",
)

except OperationalError:
# Handle the case where the database is not ready yet
pass
pass
14 changes: 12 additions & 2 deletions members/forms/volunteer_request_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
Expand All @@ -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 = [
Expand All @@ -67,6 +74,7 @@ class Meta:
"info_reference",
"info_whishes",
"department_list",
"email_token",
]

def __init__(self, *args, **kwargs):
Expand All @@ -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()

Expand All @@ -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",
),
),
Expand Down
38 changes: 23 additions & 15 deletions members/models/emailitem.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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 + "'"
6 changes: 5 additions & 1 deletion members/models/emailtemplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,20 @@ 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))
)

# figure out receiver
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:
Expand Down
8 changes: 0 additions & 8 deletions members/models/volunteerrequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
"""
57 changes: 55 additions & 2 deletions members/templates/members/volunteer_request.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,64 @@ <h1>Ønsker du at være frivillig - og ikke allerede oprettet i medlemssystemet
{% endfor %}
{% endif %}

<form method="post">
<form method="post" id="volunteer-request-form">
{% csrf_token %}
{{ volunteer_request_form|crispy }}
<button type="submit" class="btn btn-primary">Submit</button>
{% if not user.is_authenticated %}
<button type="button" class="btn btn-secondary" id="generate-code-button">Generate Code and Send to Email</button>
<div id="email-token-field" style="display: none;">
<label for="id_email_token">Indtast den 6-cifrede kode sendt til din email</label>
<!--input type="text" id="id_email_token2" name="email_token" maxlength="6" class="form-control"-->
</div>
{% endif %}
<button type="submit" class="btn btn-primary" id="submit-button" {% if not user.is_authenticated %}style="display: none;" disabled{% endif %}>Submit</button>
</form>
</div>
</div>

{% if not user.is_authenticated %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const generateCodeButton = document.getElementById('generate-code-button');
const submitButton = document.getElementById('submit-button');
const emailTokenField = document.getElementById('id_email_token');
const emailTokenFieldContainer = document.getElementById('email-token-field');

generateCodeButton.addEventListener('click', function() {
// Make an AJAX request to generate the code and send it to the email
fetch("{% url 'generate_code' %}", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token }}'
},
body: JSON.stringify({
email: document.getElementById('id_email').value
})
}).then(response => response.json()).then(data => {
if (data.success) {
alert('A 6-digit code has been sent to your email.');
generateCodeButton.style.display = 'none';
emailTokenFieldContainer.style.display = 'block';
emailTokenField.type = 'text'; // Make the field visible
submitButton.style.display = 'inline-block';
} else {
alert('Failed to send the code. Please try again.');
}
}).catch(error => {
console.error('Error:', error);
alert('Failed to send the code. Please try again.');
});
});

emailTokenField.addEventListener('input', function() {
if (emailTokenField.value.length === 6) {
submitButton.disabled = false;
} else {
submitButton.disabled = true;
}
});
});
</script>
{% endif %}
{% endblock %}
7 changes: 6 additions & 1 deletion members/urls.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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"),
Expand Down
81 changes: 78 additions & 3 deletions members/views/VolunteerRequest.py
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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()

Expand All @@ -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(
Expand Down
Loading

0 comments on commit 5a191bd

Please sign in to comment.