From e9dff746b94264b9f0b447756004294a789b88c6 Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Tue, 17 Dec 2024 15:36:00 +0000 Subject: [PATCH 01/22] adapting admin_email/admin.py --- django/admin_email/admin.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/django/admin_email/admin.py b/django/admin_email/admin.py index aa647e3e..b5fc57cd 100644 --- a/django/admin_email/admin.py +++ b/django/admin_email/admin.py @@ -2,6 +2,7 @@ from django.contrib import admin from django_mail_admin.models import OutgoingEmail from django_mail_admin.admin import OutgoingEmailAdmin, LogInline from django.core.mail import EmailMessage +from django_nano_account.models import User from .forms import CustomOutgoingEmailAdminForm import logging @@ -24,15 +25,18 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): """ # if email has not already been sent, attempt to send it if obj.status != 0: - - email = EmailMessage( - subject=obj.subject, - body=obj.message, - from_email=obj.from_email, - to=obj.to, - cc=obj.cc, - bcc=obj.bcc - ) + # Filter users who have opted in + recipients = User.objects.filter(email__in=obj.to, email_opt_in=True).values_list('email', flat=True) + + if recipients: + email = EmailMessage( + subject=obj.subject, + body=obj.message, + from_email=obj.from_email, + to=recipients, + cc=obj.cc, + bcc=obj.bcc + ) try: email.send(fail_silently=False) -- GitLab From cea620b367574b94b2c0baa134cd3734f915f32b Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Mon, 13 Jan 2025 15:59:42 +0000 Subject: [PATCH 02/22] add button to send to all mail subscribers --- django/admin_email/admin.py | 32 ++++++++++++++++++++------------ django/admin_email/forms.py | 5 ++++- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/django/admin_email/admin.py b/django/admin_email/admin.py index b5fc57cd..c911ff4c 100644 --- a/django/admin_email/admin.py +++ b/django/admin_email/admin.py @@ -21,13 +21,20 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): def save_model(self, request, obj, form, change): """ - Override method to send email (instead of queuing) on saving + Override method to send email (instead of queuing) on saving. """ - # if email has not already been sent, attempt to send it + # If email has not already been sent, attempt to send it if obj.status != 0: - # Filter users who have opted in - recipients = User.objects.filter(email__in=obj.to, email_opt_in=True).values_list('email', flat=True) - + send_to_opt_in_users = form.cleaned_data.get('send_to_opt_in_users', False) + + # Determine recipients based on the checkbox + if send_to_opt_in_users: + # Send to all users who opted in + recipients = User.objects.filter(email_opt_in=True).values_list('email', flat=True) + else: + # Send to the specific recipients in `obj.to` + recipients = User.objects.filter(email__in=obj.to, email_opt_in=True).values_list('email', flat=True) + if recipients: email = EmailMessage( subject=obj.subject, @@ -38,14 +45,15 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): bcc=obj.bcc ) - try: - email.send(fail_silently=False) - obj.status = 0 # sent - except Exception as e: - obj.status = 1 # failed - super(OutgoingEmailAdmin, self).save_model(request, obj, form, change) - logger.exception(e) + try: + # Attempt to send the email + email.send(fail_silently=False) + obj.status = 0 # Mark as sent + except Exception as e: + obj.status = 1 # Mark as failed + logger.exception(f"Failed to send email: {e}") + # Save the updated status and ensure default behavior is invoked super(OutgoingEmailAdmin, self).save_model(request, obj, form, change) diff --git a/django/admin_email/forms.py b/django/admin_email/forms.py index 1de752b0..c7dbf097 100644 --- a/django/admin_email/forms.py +++ b/django/admin_email/forms.py @@ -1,4 +1,5 @@ from django_mail_admin.forms import OutgoingEmailAdminForm +from django import forms from django.conf import settings @@ -6,8 +7,10 @@ class CustomOutgoingEmailAdminForm(OutgoingEmailAdminForm): """ Customised form inherited from `django_mail_admin.forms.OutgoingEmailAdminForm` """ + send_to_opt_in_users = forms.BooleanField(required=False, label="Send to All Mailing List Subscribers") + class Meta(OutgoingEmailAdminForm.Meta): - fields = ['from_email', 'to', 'cc', 'bcc', 'subject', 'message'] + fields = ['from_email', 'to', 'cc', 'bcc', 'subject', 'message', 'send_to_opt_in_users'] def __init__(self, *args, **kwargs): """ -- GitLab From 0c3573f58ca25ef828a743db6edb7bff1c6f1736 Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Tue, 14 Jan 2025 09:36:12 +0000 Subject: [PATCH 03/22] Added unsubscribe and email template --- django/admin_email/admin.py | 28 ++++++++++------------- django/admin_email/forms.py | 2 +- django/admin_email/urls.py | 6 +++++ django/admin_email/views.py | 10 ++++++++ django/core/templates/email_template.html | 5 ++++ django/core/urls.py | 1 + 6 files changed, 35 insertions(+), 17 deletions(-) create mode 100644 django/admin_email/urls.py create mode 100644 django/admin_email/views.py create mode 100644 django/core/templates/email_template.html diff --git a/django/admin_email/admin.py b/django/admin_email/admin.py index c911ff4c..ffb48d2e 100644 --- a/django/admin_email/admin.py +++ b/django/admin_email/admin.py @@ -4,6 +4,8 @@ from django_mail_admin.admin import OutgoingEmailAdmin, LogInline from django.core.mail import EmailMessage from django_nano_account.models import User from .forms import CustomOutgoingEmailAdminForm +from django.template.loader import render_to_string + import logging logger = logging.getLogger(__name__) @@ -20,40 +22,34 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): inlines = (LogInline,) def save_model(self, request, obj, form, change): - """ - Override method to send email (instead of queuing) on saving. - """ - # If email has not already been sent, attempt to send it if obj.status != 0: send_to_opt_in_users = form.cleaned_data.get('send_to_opt_in_users', False) - - # Determine recipients based on the checkbox if send_to_opt_in_users: - # Send to all users who opted in recipients = User.objects.filter(email_opt_in=True).values_list('email', flat=True) else: - # Send to the specific recipients in `obj.to` - recipients = User.objects.filter(email__in=obj.to, email_opt_in=True).values_list('email', flat=True) + recipients = obj.to if obj.to else [] + + for recipient in recipients: + email_body_with_unsubscribe = render_to_string('email_template.html', { + 'email_body': obj.message, + 'unsubscribe_url': request.build_absolute_uri(f'/unsubscribe/{recipient}') + }) - if recipients: email = EmailMessage( subject=obj.subject, - body=obj.message, + body=email_body_with_unsubscribe, from_email=obj.from_email, - to=recipients, + to=[recipient], # Wrap recipient in a list cc=obj.cc, bcc=obj.bcc ) try: - # Attempt to send the email email.send(fail_silently=False) obj.status = 0 # Mark as sent except Exception as e: obj.status = 1 # Mark as failed - logger.exception(f"Failed to send email: {e}") - - # Save the updated status and ensure default behavior is invoked + logger.exception(f"Failed to send email to {recipient}: {e}") super(OutgoingEmailAdmin, self).save_model(request, obj, form, change) diff --git a/django/admin_email/forms.py b/django/admin_email/forms.py index c7dbf097..5242769d 100644 --- a/django/admin_email/forms.py +++ b/django/admin_email/forms.py @@ -10,7 +10,7 @@ class CustomOutgoingEmailAdminForm(OutgoingEmailAdminForm): send_to_opt_in_users = forms.BooleanField(required=False, label="Send to All Mailing List Subscribers") class Meta(OutgoingEmailAdminForm.Meta): - fields = ['from_email', 'to', 'cc', 'bcc', 'subject', 'message', 'send_to_opt_in_users'] + fields = ['from_email', 'to', 'send_to_opt_in_users', 'cc', 'bcc', 'subject', 'message'] def __init__(self, *args, **kwargs): """ diff --git a/django/admin_email/urls.py b/django/admin_email/urls.py new file mode 100644 index 00000000..e02a8e6d --- /dev/null +++ b/django/admin_email/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('<str:email>/', views.unsubscribe, name='unsubscribe'), +] diff --git a/django/admin_email/views.py b/django/admin_email/views.py new file mode 100644 index 00000000..df5ec8c7 --- /dev/null +++ b/django/admin_email/views.py @@ -0,0 +1,10 @@ +from django.http import HttpResponse +from django.shortcuts import get_object_or_404 +from django_nano_account.models import User + + +def unsubscribe(request, email): + user = get_object_or_404(User, email=email) + user.email_opt_in = False + user.save() + return HttpResponse(f"You have successfully unsubscribed {email}.") diff --git a/django/core/templates/email_template.html b/django/core/templates/email_template.html new file mode 100644 index 00000000..8a10b5ea --- /dev/null +++ b/django/core/templates/email_template.html @@ -0,0 +1,5 @@ +<!-- email_template.html --> +<p>{{ email_body }}</p> +<p> + <a href="{{ unsubscribe_url }}">Click here to unsubscribe</a> +</p> diff --git a/django/core/urls.py b/django/core/urls.py index 0e790f78..bb1644d4 100644 --- a/django/core/urls.py +++ b/django/core/urls.py @@ -11,6 +11,7 @@ urlpatterns = [ path('upload/', include('microscopy_data.urls')), path('analysis/', include('hpcsubmit.urls')), path("contact/", include("django_contact_form.urls")), + path('unsubscribe/', include('admin_email.urls')), ] + static( settings.MEDIA_URL, document_root=settings.MEDIA_ROOT ) -- GitLab From 2589387ee35b7c930d201295ab0fb1b23b0140aa Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Tue, 14 Jan 2025 10:04:58 +0000 Subject: [PATCH 04/22] improve email template --- django/core/templates/email_template.html | 104 ++++++++++++++++++++-- 1 file changed, 99 insertions(+), 5 deletions(-) diff --git a/django/core/templates/email_template.html b/django/core/templates/email_template.html index 8a10b5ea..f256c420 100644 --- a/django/core/templates/email_template.html +++ b/django/core/templates/email_template.html @@ -1,5 +1,99 @@ -<!-- email_template.html --> -<p>{{ email_body }}</p> -<p> - <a href="{{ unsubscribe_url }}">Click here to unsubscribe</a> -</p> +<!DOCTYPE html> +<html lang="en"> +<head> + <title>email_template</title> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <style> + body { + min-height: 100vh; + background: white; + font-family: 'Didact Gothic', sans-serif; + color: black; + display: grid; + grid-template-rows: auto 1fr auto; + } + .container { + width: 100%; + max-width: 600px; + margin: 0 auto; + padding: 20px; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: 5px; + } + .header, .footer { + background-color: #007BFF; + color: #fff; + text-align: center; + padding: 10px 0; + } + .content { + margin-bottom: 20px; + } + .unsubscribe { + text-align: center; + margin-top: 20px; + } + .unsubscribe a { + color: #007BFF; + text-decoration: none; + } + .unsubscribe a:hover { + text-decoration: underline; + } + a { + color: #714316; + text-decoration: underline; + } + a:hover { + color: #7d2604; + } + h1, h2 { + color: #995c38; + } + footer { + width: 100%; + height: auto; + line-height: 20px; + padding-top: 20px; + text-align: center; + color: #482b0e; + background-color: #fdb44b; + z-index: 1000; + } + footer a { + color: #4a3212; + } + </style> +</head> +<body> + <div class="container"> + <div class="content"> + {{ email_body }} + </div> + <div class="unsubscribe"> + <a href="{{ unsubscribe_url }}" style="display: inline-block; padding: 10px 20px; font-size: 16px; color: #fff; background-color: #007BFF; text-decoration: none; border-radius: 5px;">Unsubscribe from Mailing List</a> + </div> + <br> + <footer id="footer" role="contentinfo"> + <p> + Developed by the + <a href="https://www.birmingham.ac.uk/bear-software">Research Software Group</a> + at the + <a href="https://www.bham.ac.uk">University of Birmingham</a> + </p> + <p> + <a href="{% url 'general:user_agreement' %}">User Agreement</a> | + <a href="{% url 'django_contact_form' %}">Contact us</a> | + <a href="https://www.birmingham.ac.uk/privacy/index.aspx" target="_blank">Privacy</a> | + <a href="https://www.birmingham.ac.uk/legal/index.aspx" target="_blank">Legal</a> | + <a href="https://www.birmingham.ac.uk/university/governance/policies-regs/information/index.aspx" target="_blank">Freedom of Information</a> | + <a href="{% url 'general:cookies' %}">Cookies</a> | + <a href="https://accessibility.bear.bham.ac.uk/statements-nano-org.html" target="_blank">Accessibility</a> + </p> + </footer> + </div> +</body> +</html> \ No newline at end of file -- GitLab From 43586e5d5a8fbadf1ca992284f67912dc9081973 Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Tue, 14 Jan 2025 10:12:22 +0000 Subject: [PATCH 05/22] tried to add Unsubscribe Confirmation page --- django/admin_email/views.py | 4 ++-- .../templates/unsubscribe_confirmation.html | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 django/core/templates/unsubscribe_confirmation.html diff --git a/django/admin_email/views.py b/django/admin_email/views.py index df5ec8c7..0c4fc8ee 100644 --- a/django/admin_email/views.py +++ b/django/admin_email/views.py @@ -1,5 +1,5 @@ from django.http import HttpResponse -from django.shortcuts import get_object_or_404 +from django.shortcuts import render, get_object_or_404 from django_nano_account.models import User @@ -7,4 +7,4 @@ def unsubscribe(request, email): user = get_object_or_404(User, email=email) user.email_opt_in = False user.save() - return HttpResponse(f"You have successfully unsubscribed {email}.") + return render(request, 'unsubscribe_confirmation.html', {'email': email}) diff --git a/django/core/templates/unsubscribe_confirmation.html b/django/core/templates/unsubscribe_confirmation.html new file mode 100644 index 00000000..a4203b04 --- /dev/null +++ b/django/core/templates/unsubscribe_confirmation.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Unsubscribe Confirmation</title> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.2/dist/css/bootstrap.min.css" integrity="sha384-uWxY/CJNBR+1zjPWmfnSnVxwRheevXITnMqoEIeG1LJrdI0GlVs/9cVSyPYXdcSF" crossorigin="anonymous"> + <link rel="stylesheet" href="{% static 'css/style.css' %}"> +</head> +<body> + <div class="container mt-5"> + <div class="card"> + <div class="card-body text-center"> + <h1 class="card-title">Unsubscribe Successful</h1> + <p class="card-text">You have successfully unsubscribed {{ email }}.</p> + + </div> + </div> + </div> +</body> +</html> \ No newline at end of file -- GitLab From 20667857a320c516b377fde937aa03b1af6f0d1d Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Tue, 14 Jan 2025 14:55:53 +0000 Subject: [PATCH 06/22] update django-n-account dir --- django-nano-account | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django-nano-account b/django-nano-account index 9596f67f..722fc739 160000 --- a/django-nano-account +++ b/django-nano-account @@ -1 +1 @@ -Subproject commit 9596f67f87ba3cc7eaa13b7ba404aa431c349848 +Subproject commit 722fc739630ffa72bcd2b2687f72424263052e8d -- GitLab From 17eb6e0c32defbbfef0f87bb38ce93b259dafcf5 Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Tue, 14 Jan 2025 17:27:35 +0000 Subject: [PATCH 07/22] fixing the html email rendering --- django/admin_email/admin.py | 8 +++++--- django/admin_email/views.py | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/django/admin_email/admin.py b/django/admin_email/admin.py index ffb48d2e..74d0b0fb 100644 --- a/django/admin_email/admin.py +++ b/django/admin_email/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from django_mail_admin.models import OutgoingEmail from django_mail_admin.admin import OutgoingEmailAdmin, LogInline -from django.core.mail import EmailMessage +from django.core.mail import EmailMultiAlternatives from django_nano_account.models import User from .forms import CustomOutgoingEmailAdminForm from django.template.loader import render_to_string @@ -35,15 +35,17 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): 'unsubscribe_url': request.build_absolute_uri(f'/unsubscribe/{recipient}') }) - email = EmailMessage( + email = EmailMultiAlternatives( subject=obj.subject, body=email_body_with_unsubscribe, from_email=obj.from_email, - to=[recipient], # Wrap recipient in a list + to=[recipient], cc=obj.cc, bcc=obj.bcc ) + email.attach_alternative(email_body_with_unsubscribe, "text/html") + try: email.send(fail_silently=False) obj.status = 0 # Mark as sent diff --git a/django/admin_email/views.py b/django/admin_email/views.py index 0c4fc8ee..1c18391e 100644 --- a/django/admin_email/views.py +++ b/django/admin_email/views.py @@ -1,4 +1,3 @@ -from django.http import HttpResponse from django.shortcuts import render, get_object_or_404 from django_nano_account.models import User -- GitLab From 12df383be563a7005ca3581c59a84fed1b89c25b Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Wed, 15 Jan 2025 15:47:16 +0000 Subject: [PATCH 08/22] add template for direct email and try to fix unsubscribe page --- django/admin_email/admin.py | 19 ++-- django/admin_email/views.py | 11 ++- django/core/templates/email_template.html | 14 --- .../email_template_for_mail_list.html | 99 +++++++++++++++++++ 4 files changed, 122 insertions(+), 21 deletions(-) create mode 100644 django/core/templates/email_template_for_mail_list.html diff --git a/django/admin_email/admin.py b/django/admin_email/admin.py index 74d0b0fb..9061f1bc 100644 --- a/django/admin_email/admin.py +++ b/django/admin_email/admin.py @@ -5,6 +5,7 @@ from django.core.mail import EmailMultiAlternatives from django_nano_account.models import User from .forms import CustomOutgoingEmailAdminForm from django.template.loader import render_to_string +from urllib.parse import quote import logging @@ -26,25 +27,31 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): send_to_opt_in_users = form.cleaned_data.get('send_to_opt_in_users', False) if send_to_opt_in_users: recipients = User.objects.filter(email_opt_in=True).values_list('email', flat=True) + template_name = 'email_template_for_mail_list.html' else: recipients = obj.to if obj.to else [] + template_name = 'email_template.html' for recipient in recipients: - email_body_with_unsubscribe = render_to_string('email_template.html', { - 'email_body': obj.message, - 'unsubscribe_url': request.build_absolute_uri(f'/unsubscribe/{recipient}') - }) + context = {'email_body': obj.message} + + # Add unsubscribe URL only if send_to_opt_in_users is True + if send_to_opt_in_users: + context['unsubscribe_url'] = request.build_absolute_uri(f'/unsubscribe/{quote(recipient)}/') + + # Render the email body using the selected template + email_body_final = render_to_string(template_name, context) email = EmailMultiAlternatives( subject=obj.subject, - body=email_body_with_unsubscribe, + body=obj.message, from_email=obj.from_email, to=[recipient], cc=obj.cc, bcc=obj.bcc ) - email.attach_alternative(email_body_with_unsubscribe, "text/html") + email.attach_alternative(email_body_final, "text/html") try: email.send(fail_silently=False) diff --git a/django/admin_email/views.py b/django/admin_email/views.py index 1c18391e..39f8cd11 100644 --- a/django/admin_email/views.py +++ b/django/admin_email/views.py @@ -1,9 +1,18 @@ from django.shortcuts import render, get_object_or_404 from django_nano_account.models import User +from django.http import Http404 +from urllib.parse import unquote def unsubscribe(request, email): - user = get_object_or_404(User, email=email) + email = unquote(email) + + try: + user = User.objects.get(email=email) + except User.DoesNotExist: + raise Http404(f"No user found with email {email}") + user.email_opt_in = False user.save() return render(request, 'unsubscribe_confirmation.html', {'email': email}) + diff --git a/django/core/templates/email_template.html b/django/core/templates/email_template.html index f256c420..cc379dcf 100644 --- a/django/core/templates/email_template.html +++ b/django/core/templates/email_template.html @@ -32,17 +32,6 @@ .content { margin-bottom: 20px; } - .unsubscribe { - text-align: center; - margin-top: 20px; - } - .unsubscribe a { - color: #007BFF; - text-decoration: none; - } - .unsubscribe a:hover { - text-decoration: underline; - } a { color: #714316; text-decoration: underline; @@ -73,9 +62,6 @@ <div class="content"> {{ email_body }} </div> - <div class="unsubscribe"> - <a href="{{ unsubscribe_url }}" style="display: inline-block; padding: 10px 20px; font-size: 16px; color: #fff; background-color: #007BFF; text-decoration: none; border-radius: 5px;">Unsubscribe from Mailing List</a> - </div> <br> <footer id="footer" role="contentinfo"> <p> diff --git a/django/core/templates/email_template_for_mail_list.html b/django/core/templates/email_template_for_mail_list.html new file mode 100644 index 00000000..f256c420 --- /dev/null +++ b/django/core/templates/email_template_for_mail_list.html @@ -0,0 +1,99 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title>email_template</title> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <style> + body { + min-height: 100vh; + background: white; + font-family: 'Didact Gothic', sans-serif; + color: black; + display: grid; + grid-template-rows: auto 1fr auto; + } + .container { + width: 100%; + max-width: 600px; + margin: 0 auto; + padding: 20px; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: 5px; + } + .header, .footer { + background-color: #007BFF; + color: #fff; + text-align: center; + padding: 10px 0; + } + .content { + margin-bottom: 20px; + } + .unsubscribe { + text-align: center; + margin-top: 20px; + } + .unsubscribe a { + color: #007BFF; + text-decoration: none; + } + .unsubscribe a:hover { + text-decoration: underline; + } + a { + color: #714316; + text-decoration: underline; + } + a:hover { + color: #7d2604; + } + h1, h2 { + color: #995c38; + } + footer { + width: 100%; + height: auto; + line-height: 20px; + padding-top: 20px; + text-align: center; + color: #482b0e; + background-color: #fdb44b; + z-index: 1000; + } + footer a { + color: #4a3212; + } + </style> +</head> +<body> + <div class="container"> + <div class="content"> + {{ email_body }} + </div> + <div class="unsubscribe"> + <a href="{{ unsubscribe_url }}" style="display: inline-block; padding: 10px 20px; font-size: 16px; color: #fff; background-color: #007BFF; text-decoration: none; border-radius: 5px;">Unsubscribe from Mailing List</a> + </div> + <br> + <footer id="footer" role="contentinfo"> + <p> + Developed by the + <a href="https://www.birmingham.ac.uk/bear-software">Research Software Group</a> + at the + <a href="https://www.bham.ac.uk">University of Birmingham</a> + </p> + <p> + <a href="{% url 'general:user_agreement' %}">User Agreement</a> | + <a href="{% url 'django_contact_form' %}">Contact us</a> | + <a href="https://www.birmingham.ac.uk/privacy/index.aspx" target="_blank">Privacy</a> | + <a href="https://www.birmingham.ac.uk/legal/index.aspx" target="_blank">Legal</a> | + <a href="https://www.birmingham.ac.uk/university/governance/policies-regs/information/index.aspx" target="_blank">Freedom of Information</a> | + <a href="{% url 'general:cookies' %}">Cookies</a> | + <a href="https://accessibility.bear.bham.ac.uk/statements-nano-org.html" target="_blank">Accessibility</a> + </p> + </footer> + </div> +</body> +</html> \ No newline at end of file -- GitLab From e889b7dfdbf25c2937a14510afe80dda2a8d732c Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Wed, 15 Jan 2025 16:06:00 +0000 Subject: [PATCH 09/22] update unsub page and delete unused import --- django/admin_email/views.py | 3 +- .../templates/unsubscribe_confirmation.html | 29 +++++++------------ 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/django/admin_email/views.py b/django/admin_email/views.py index 39f8cd11..7164487d 100644 --- a/django/admin_email/views.py +++ b/django/admin_email/views.py @@ -1,4 +1,4 @@ -from django.shortcuts import render, get_object_or_404 +from django.shortcuts import render from django_nano_account.models import User from django.http import Http404 from urllib.parse import unquote @@ -15,4 +15,3 @@ def unsubscribe(request, email): user.email_opt_in = False user.save() return render(request, 'unsubscribe_confirmation.html', {'email': email}) - diff --git a/django/core/templates/unsubscribe_confirmation.html b/django/core/templates/unsubscribe_confirmation.html index a4203b04..a1eef99f 100644 --- a/django/core/templates/unsubscribe_confirmation.html +++ b/django/core/templates/unsubscribe_confirmation.html @@ -1,21 +1,12 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Unsubscribe Confirmation</title> - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.2/dist/css/bootstrap.min.css" integrity="sha384-uWxY/CJNBR+1zjPWmfnSnVxwRheevXITnMqoEIeG1LJrdI0GlVs/9cVSyPYXdcSF" crossorigin="anonymous"> - <link rel="stylesheet" href="{% static 'css/style.css' %}"> -</head> -<body> - <div class="container mt-5"> - <div class="card"> - <div class="card-body text-center"> - <h1 class="card-title">Unsubscribe Successful</h1> - <p class="card-text">You have successfully unsubscribed {{ email }}.</p> - - </div> +{% extends "base.html" %} + +{% block content %} +<div class="container mt-5"> + <div class="card"> + <div class="card-body text-center"> + <h1 class="card-title">Unsubscribe Successful</h1> + <p class="card-text">You have successfully unsubscribed {{ email }}.</p> </div> </div> -</body> -</html> \ No newline at end of file +</div> +{% endblock %} -- GitLab From fca3e7cc7481ff6d34495819520d90b2f91d560b Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Wed, 15 Jan 2025 16:17:06 +0000 Subject: [PATCH 10/22] correcting html indents for unsub --- .../core/templates/unsubscribe_confirmation.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/django/core/templates/unsubscribe_confirmation.html b/django/core/templates/unsubscribe_confirmation.html index a1eef99f..aa625669 100644 --- a/django/core/templates/unsubscribe_confirmation.html +++ b/django/core/templates/unsubscribe_confirmation.html @@ -1,12 +1,12 @@ {% extends "base.html" %} -{% block content %} -<div class="container mt-5"> - <div class="card"> - <div class="card-body text-center"> - <h1 class="card-title">Unsubscribe Successful</h1> - <p class="card-text">You have successfully unsubscribed {{ email }}.</p> + {% block content %} + <div class="container mt-5"> + <div class="card"> + <div class="card-body text-center"> + <h1 class="card-title">Unsubscribe Successful</h1> + <p class="card-text">You have successfully unsubscribed {{ email }}.</p> + </div> + </div> </div> - </div> -</div> {% endblock %} -- GitLab From bdd967a2978eba61f7aa77a2feea8d65e369555d Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Wed, 15 Jan 2025 16:27:31 +0000 Subject: [PATCH 11/22] update django-nano-account --- django-nano-account | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django-nano-account b/django-nano-account index 722fc739..a42b9816 160000 --- a/django-nano-account +++ b/django-nano-account @@ -1 +1 @@ -Subproject commit 722fc739630ffa72bcd2b2687f72424263052e8d +Subproject commit a42b9816b51d2d85dfe5df44d79671e56c2393d0 -- GitLab From 3e4c2f35df85afa4846cf851b2bc4d068788b152 Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Wed, 15 Jan 2025 16:29:30 +0000 Subject: [PATCH 12/22] try identation unsub html --- .../core/templates/unsubscribe_confirmation.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/django/core/templates/unsubscribe_confirmation.html b/django/core/templates/unsubscribe_confirmation.html index aa625669..a1eef99f 100644 --- a/django/core/templates/unsubscribe_confirmation.html +++ b/django/core/templates/unsubscribe_confirmation.html @@ -1,12 +1,12 @@ {% extends "base.html" %} - {% block content %} - <div class="container mt-5"> - <div class="card"> - <div class="card-body text-center"> - <h1 class="card-title">Unsubscribe Successful</h1> - <p class="card-text">You have successfully unsubscribed {{ email }}.</p> - </div> - </div> +{% block content %} +<div class="container mt-5"> + <div class="card"> + <div class="card-body text-center"> + <h1 class="card-title">Unsubscribe Successful</h1> + <p class="card-text">You have successfully unsubscribed {{ email }}.</p> </div> + </div> +</div> {% endblock %} -- GitLab From 88dcb268e86dc8f208396ae056e32eb963cb6a94 Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Wed, 15 Jan 2025 17:03:02 +0000 Subject: [PATCH 13/22] Update to consider linebreaks in mail --- django/admin_email/admin.py | 3 ++- django/core/templates/email_template.html | 2 +- django/core/templates/email_template_for_mail_list.html | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/django/admin_email/admin.py b/django/admin_email/admin.py index 9061f1bc..870b7825 100644 --- a/django/admin_email/admin.py +++ b/django/admin_email/admin.py @@ -2,6 +2,7 @@ from django.contrib import admin from django_mail_admin.models import OutgoingEmail from django_mail_admin.admin import OutgoingEmailAdmin, LogInline from django.core.mail import EmailMultiAlternatives +from django.utils.html import linebreaks from django_nano_account.models import User from .forms import CustomOutgoingEmailAdminForm from django.template.loader import render_to_string @@ -33,7 +34,7 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): template_name = 'email_template.html' for recipient in recipients: - context = {'email_body': obj.message} + context = {'email_body': linebreaks(obj.message)} # Add unsubscribe URL only if send_to_opt_in_users is True if send_to_opt_in_users: diff --git a/django/core/templates/email_template.html b/django/core/templates/email_template.html index cc379dcf..be169388 100644 --- a/django/core/templates/email_template.html +++ b/django/core/templates/email_template.html @@ -60,7 +60,7 @@ <body> <div class="container"> <div class="content"> - {{ email_body }} + {{ email_body|linebreaks }} </div> <br> <footer id="footer" role="contentinfo"> diff --git a/django/core/templates/email_template_for_mail_list.html b/django/core/templates/email_template_for_mail_list.html index f256c420..2942ed9b 100644 --- a/django/core/templates/email_template_for_mail_list.html +++ b/django/core/templates/email_template_for_mail_list.html @@ -71,7 +71,7 @@ <body> <div class="container"> <div class="content"> - {{ email_body }} + {{ email_body|linebreaks }} </div> <div class="unsubscribe"> <a href="{{ unsubscribe_url }}" style="display: inline-block; padding: 10px 20px; font-size: 16px; color: #fff; background-color: #007BFF; text-decoration: none; border-radius: 5px;">Unsubscribe from Mailing List</a> -- GitLab From 930d8ef5d200b0ba102f5a1690c8fc571575e2ec Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Wed, 15 Jan 2025 17:27:05 +0000 Subject: [PATCH 14/22] Changing email template colour and writing --- django/core/templates/email_template.html | 6 +++--- django/core/templates/email_template_for_mail_list.html | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/django/core/templates/email_template.html b/django/core/templates/email_template.html index be169388..7ce1af78 100644 --- a/django/core/templates/email_template.html +++ b/django/core/templates/email_template.html @@ -24,8 +24,8 @@ border-radius: 5px; } .header, .footer { - background-color: #007BFF; - color: #fff; + background-color: #fdb44b; + color: black; text-align: center; padding: 10px 0; } @@ -65,7 +65,7 @@ <br> <footer id="footer" role="contentinfo"> <p> - Developed by the + nano-org website has been developed by the <a href="https://www.birmingham.ac.uk/bear-software">Research Software Group</a> at the <a href="https://www.bham.ac.uk">University of Birmingham</a> diff --git a/django/core/templates/email_template_for_mail_list.html b/django/core/templates/email_template_for_mail_list.html index 2942ed9b..4b8afb93 100644 --- a/django/core/templates/email_template_for_mail_list.html +++ b/django/core/templates/email_template_for_mail_list.html @@ -24,8 +24,8 @@ border-radius: 5px; } .header, .footer { - background-color: #007BFF; - color: #fff; + background-color: #fdb44b; + color: black; text-align: center; padding: 10px 0; } @@ -37,7 +37,7 @@ margin-top: 20px; } .unsubscribe a { - color: #007BFF; + color: #fdb44b; text-decoration: none; } .unsubscribe a:hover { @@ -79,7 +79,7 @@ <br> <footer id="footer" role="contentinfo"> <p> - Developed by the + nano-org website has been developed by the <a href="https://www.birmingham.ac.uk/bear-software">Research Software Group</a> at the <a href="https://www.bham.ac.uk">University of Birmingham</a> -- GitLab From 81b8c68e3870c8168bf8e59f53f8780d5636a42e Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Wed, 15 Jan 2025 17:33:05 +0000 Subject: [PATCH 15/22] Correcting colour change --- django/core/templates/email_template_for_mail_list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/core/templates/email_template_for_mail_list.html b/django/core/templates/email_template_for_mail_list.html index 4b8afb93..2d88a5d4 100644 --- a/django/core/templates/email_template_for_mail_list.html +++ b/django/core/templates/email_template_for_mail_list.html @@ -74,7 +74,7 @@ {{ email_body|linebreaks }} </div> <div class="unsubscribe"> - <a href="{{ unsubscribe_url }}" style="display: inline-block; padding: 10px 20px; font-size: 16px; color: #fff; background-color: #007BFF; text-decoration: none; border-radius: 5px;">Unsubscribe from Mailing List</a> + <a href="{{ unsubscribe_url }}" style="display: inline-block; padding: 10px 20px; font-size: 16px; color: black; background-color: #fdb44b; text-decoration: none; border-radius: 5px;">Unsubscribe from Mailing List</a> </div> <br> <footer id="footer" role="contentinfo"> -- GitLab From a5838e4bdbbc0a5ff7d60213d9771803c24aa542 Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Thu, 16 Jan 2025 10:15:06 +0000 Subject: [PATCH 16/22] Add updated django nano account --- django-nano-account | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django-nano-account b/django-nano-account index a42b9816..f021203f 160000 --- a/django-nano-account +++ b/django-nano-account @@ -1 +1 @@ -Subproject commit a42b9816b51d2d85dfe5df44d79671e56c2393d0 +Subproject commit f021203f5c814f6ac9d9d722194cef12bb5a9814 -- GitLab From fd2d7bc47d17a4f7a1b744882fb52cf20d48779e Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Thu, 16 Jan 2025 10:30:16 +0000 Subject: [PATCH 17/22] Initial Mailing List Sign Up Email --- django/admin_email/admin.py | 28 ++++++ django/admin_email/urls.py | 1 + django/admin_email/views.py | 14 ++- .../templates/signup_mailing_list_email.html | 99 +++++++++++++++++++ django/core/urls.py | 2 + 5 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 django/core/templates/signup_mailing_list_email.html diff --git a/django/admin_email/admin.py b/django/admin_email/admin.py index 870b7825..3288256d 100644 --- a/django/admin_email/admin.py +++ b/django/admin_email/admin.py @@ -62,5 +62,33 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): logger.exception(f"Failed to send email to {recipient}: {e}") super(OutgoingEmailAdmin, self).save_model(request, obj, form, change) + def send_initial_sign_in_email(self, obj, request): + recipients = User.objects.all().values_list('email', flat=True) + subject = "Stay Updated with Our Latest Analysis Features" + template_name = 'signup_mailing_list_email.html' + + for recipient in recipients: + context = { + 'email_body': "Welcome to our platform! We're excited to have you on board. Click the link below to join our mailing list and stay updated with our latest features and research papers.", + 'join_mailing_list_url': request.build_absolute_uri(f'/join_mailing_list/{quote(recipient)}/') + } + + email_body_final = render_to_string(template_name, context) + + email = EmailMultiAlternatives( + subject=subject, + body=context['email_body'], + from_email=obj.from_email, + to=[recipient] + ) + + email.attach_alternative(email_body_final, "text/html") + + try: + email.send(fail_silently=False) + logger.info(f"Sent initial sign-in email to {recipient}") + except Exception as e: + logger.exception(f"Failed to send initial sign-in email to {recipient}: {e}") + admin.site.register(OutgoingEmail, CustomOutgoingEmailAdmin) diff --git a/django/admin_email/urls.py b/django/admin_email/urls.py index e02a8e6d..da601503 100644 --- a/django/admin_email/urls.py +++ b/django/admin_email/urls.py @@ -2,5 +2,6 @@ from django.urls import path from . import views urlpatterns = [ + path('join_mailing_list/<str:email>/', views.join_mailing_list, name='join_mailing_list'), path('<str:email>/', views.unsubscribe, name='unsubscribe'), ] diff --git a/django/admin_email/views.py b/django/admin_email/views.py index 7164487d..533edae4 100644 --- a/django/admin_email/views.py +++ b/django/admin_email/views.py @@ -1,9 +1,21 @@ -from django.shortcuts import render +from django.shortcuts import render, redirect from django_nano_account.models import User from django.http import Http404 from urllib.parse import unquote +def join_mailing_list(request, email): + email = unquote(email) + + try: + user = User.objects.get(email=email) + except User.DoesNotExist: + raise Http404(f"No user found with email {email}") + + user.email_opt_in = True + user.save() + return redirect('home') + def unsubscribe(request, email): email = unquote(email) diff --git a/django/core/templates/signup_mailing_list_email.html b/django/core/templates/signup_mailing_list_email.html new file mode 100644 index 00000000..9681ff07 --- /dev/null +++ b/django/core/templates/signup_mailing_list_email.html @@ -0,0 +1,99 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title>mailing_list_email</title> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <style> + body { + min-height: 100vh; + background: white; + font-family: 'Didact Gothic', sans-serif; + color: black; + display: grid; + grid-template-rows: auto 1fr auto; + } + .container { + width: 100%; + max-width: 600px; + margin: 0 auto; + padding: 20px; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: 5px; + } + .header, .footer { + background-color: #fdb44b; + color: black; + text-align: center; + padding: 10px 0; + } + .content { + margin-bottom: 20px; + } + .unsubscribe { + text-align: center; + margin-top: 20px; + } + .unsubscribe a { + color: #fdb44b; + text-decoration: none; + } + .unsubscribe a:hover { + text-decoration: underline; + } + a { + color: #714316; + text-decoration: underline; + } + a:hover { + color: #7d2604; + } + h1, h2 { + color: #995c38; + } + footer { + width: 100%; + height: auto; + line-height: 20px; + padding-top: 20px; + text-align: center; + color: #482b0e; + background-color: #fdb44b; + z-index: 1000; + } + footer a { + color: #4a3212; + } + </style> +</head> +<body> + <div class="container"> + <div class="content"> + {{ email_body|linebreaks }} + </div> + <div class="join_mailing_list"> + <a href="{{ join_mailing_list_url }}" style="display: inline-block; padding: 10px 20px; font-size: 16px; color: black; background-color: #fdb44b; text-decoration: none; border-radius: 5px;">Join our Mailing List</a> + </div> + <br> + <footer id="footer" role="contentinfo"> + <p> + nano-org website has been developed by the + <a href="https://www.birmingham.ac.uk/bear-software">Research Software Group</a> + at the + <a href="https://www.bham.ac.uk">University of Birmingham</a> + </p> + <p> + <a href="{% url 'general:user_agreement' %}">User Agreement</a> | + <a href="{% url 'django_contact_form' %}">Contact us</a> | + <a href="https://www.birmingham.ac.uk/privacy/index.aspx" target="_blank">Privacy</a> | + <a href="https://www.birmingham.ac.uk/legal/index.aspx" target="_blank">Legal</a> | + <a href="https://www.birmingham.ac.uk/university/governance/policies-regs/information/index.aspx" target="_blank">Freedom of Information</a> | + <a href="{% url 'general:cookies' %}">Cookies</a> | + <a href="https://accessibility.bear.bham.ac.uk/statements-nano-org.html" target="_blank">Accessibility</a> + </p> + </footer> + </div> +</body> +</html> \ No newline at end of file diff --git a/django/core/urls.py b/django/core/urls.py index bb1644d4..71b2c60a 100644 --- a/django/core/urls.py +++ b/django/core/urls.py @@ -12,6 +12,8 @@ urlpatterns = [ path('analysis/', include('hpcsubmit.urls')), path("contact/", include("django_contact_form.urls")), path('unsubscribe/', include('admin_email.urls')), + path('join_mailing_list/', include('admin_email.urls')), + ] + static( settings.MEDIA_URL, document_root=settings.MEDIA_ROOT ) -- GitLab From de4f71f0b148a8c32519c33674f4c75399276137 Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Thu, 16 Jan 2025 14:46:36 +0000 Subject: [PATCH 18/22] added subscription successful page --- django/admin_email/admin.py | 17 ++++++++++++----- .../templates/emails/email_template.html | 11 +++++++++++ .../emails/unsubscribe_email_template.txt | 9 +++++++++ .../mail_admin/unsubscribe_email_template.txt | 9 +++++++++ django/admin_email/urls.py | 2 +- django/admin_email/views.py | 5 +++-- .../core/templates/subscribed_confirmation.html | 12 ++++++++++++ django/core/urls.py | 4 +--- 8 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 django/admin_email/templates/emails/email_template.html create mode 100644 django/admin_email/templates/emails/unsubscribe_email_template.txt create mode 100644 django/admin_email/templates/mail_admin/unsubscribe_email_template.txt create mode 100644 django/core/templates/subscribed_confirmation.html diff --git a/django/admin_email/admin.py b/django/admin_email/admin.py index 3288256d..86e34b02 100644 --- a/django/admin_email/admin.py +++ b/django/admin_email/admin.py @@ -22,6 +22,7 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): form = CustomOutgoingEmailAdminForm list_display = ['id', 'to_display', 'subject', 'from_email', 'status'] inlines = (LogInline,) + actions = ['send_initial_sign_in_email'] def save_model(self, request, obj, form, change): if obj.status != 0: @@ -38,7 +39,7 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): # Add unsubscribe URL only if send_to_opt_in_users is True if send_to_opt_in_users: - context['unsubscribe_url'] = request.build_absolute_uri(f'/unsubscribe/{quote(recipient)}/') + context['unsubscribe_url'] = request.build_absolute_uri(f'/email/unsubscribe/{quote(recipient)}/') # Render the email body using the selected template email_body_final = render_to_string(template_name, context) @@ -62,15 +63,17 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): logger.exception(f"Failed to send email to {recipient}: {e}") super(OutgoingEmailAdmin, self).save_model(request, obj, form, change) - def send_initial_sign_in_email(self, obj, request): - recipients = User.objects.all().values_list('email', flat=True) + def send_initial_sign_in_email(self, request, queryset): + # recipients are those who have not signedup yet + recipients = User.objects.filter(email_opt_in=False).values_list('email', flat=True) + subject = "Stay Updated with Our Latest Analysis Features" template_name = 'signup_mailing_list_email.html' for recipient in recipients: context = { 'email_body': "Welcome to our platform! We're excited to have you on board. Click the link below to join our mailing list and stay updated with our latest features and research papers.", - 'join_mailing_list_url': request.build_absolute_uri(f'/join_mailing_list/{quote(recipient)}/') + 'join_mailing_list_url': request.build_absolute_uri(f'/email/join_mailing_list/{quote(recipient)}/') } email_body_final = render_to_string(template_name, context) @@ -78,7 +81,7 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): email = EmailMultiAlternatives( subject=subject, body=context['email_body'], - from_email=obj.from_email, + from_email='your_email@example.com', to=[recipient] ) @@ -90,5 +93,9 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): except Exception as e: logger.exception(f"Failed to send initial sign-in email to {recipient}: {e}") + self.message_user(request, "Initial sign-in emails have been sent.") + + send_initial_sign_in_email.short_description = "Send mailing list email to users not signed up" + admin.site.register(OutgoingEmail, CustomOutgoingEmailAdmin) diff --git a/django/admin_email/templates/emails/email_template.html b/django/admin_email/templates/emails/email_template.html new file mode 100644 index 00000000..cd36d67a --- /dev/null +++ b/django/admin_email/templates/emails/email_template.html @@ -0,0 +1,11 @@ +!DOCTYPE html> +<html> +<head> + <title>Email</title> +</head> +<body> + <p>Dear {{ user.username }},</p> + <p>Here is the message content.</p> + <p>Best regards,<br> Helen</p> +</body> +</html> \ No newline at end of file diff --git a/django/admin_email/templates/emails/unsubscribe_email_template.txt b/django/admin_email/templates/emails/unsubscribe_email_template.txt new file mode 100644 index 00000000..11033354 --- /dev/null +++ b/django/admin_email/templates/emails/unsubscribe_email_template.txt @@ -0,0 +1,9 @@ +Hello {{ user.first_name }}, + +{{ message }} + +If you wish to unsubscribe from future emails, click the link below: +{{ unsubscribe_url }} + +Thank you, +The Team diff --git a/django/admin_email/templates/mail_admin/unsubscribe_email_template.txt b/django/admin_email/templates/mail_admin/unsubscribe_email_template.txt new file mode 100644 index 00000000..11033354 --- /dev/null +++ b/django/admin_email/templates/mail_admin/unsubscribe_email_template.txt @@ -0,0 +1,9 @@ +Hello {{ user.first_name }}, + +{{ message }} + +If you wish to unsubscribe from future emails, click the link below: +{{ unsubscribe_url }} + +Thank you, +The Team diff --git a/django/admin_email/urls.py b/django/admin_email/urls.py index da601503..6488b1d0 100644 --- a/django/admin_email/urls.py +++ b/django/admin_email/urls.py @@ -3,5 +3,5 @@ from . import views urlpatterns = [ path('join_mailing_list/<str:email>/', views.join_mailing_list, name='join_mailing_list'), - path('<str:email>/', views.unsubscribe, name='unsubscribe'), + path('unsubscribe/<str:email>/', views.unsubscribe, name='unsubscribe'), ] diff --git a/django/admin_email/views.py b/django/admin_email/views.py index 533edae4..66c3f066 100644 --- a/django/admin_email/views.py +++ b/django/admin_email/views.py @@ -1,4 +1,4 @@ -from django.shortcuts import render, redirect +from django.shortcuts import render from django_nano_account.models import User from django.http import Http404 from urllib.parse import unquote @@ -14,7 +14,8 @@ def join_mailing_list(request, email): user.email_opt_in = True user.save() - return redirect('home') + return render(request, 'subscribed_confirmation.html', {'email': email}) + def unsubscribe(request, email): email = unquote(email) diff --git a/django/core/templates/subscribed_confirmation.html b/django/core/templates/subscribed_confirmation.html new file mode 100644 index 00000000..98540181 --- /dev/null +++ b/django/core/templates/subscribed_confirmation.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} + +{% block content %} +<div class="container mt-5"> + <div class="card"> + <div class="card-body text-center"> + <h1 class="card-title">Subscribed to Mailing List!</h1> + <p class="card-text">You have successfully subscribed to our mailing list {{ email }}.</p> + </div> + </div> +</div> +{% endblock %} diff --git a/django/core/urls.py b/django/core/urls.py index 71b2c60a..610333f2 100644 --- a/django/core/urls.py +++ b/django/core/urls.py @@ -11,9 +11,7 @@ urlpatterns = [ path('upload/', include('microscopy_data.urls')), path('analysis/', include('hpcsubmit.urls')), path("contact/", include("django_contact_form.urls")), - path('unsubscribe/', include('admin_email.urls')), - path('join_mailing_list/', include('admin_email.urls')), - + path('email/', include('admin_email.urls')), ] + static( settings.MEDIA_URL, document_root=settings.MEDIA_ROOT ) -- GitLab From e49fc9d3c130a941ed32b56dd761c095584938d8 Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Thu, 16 Jan 2025 14:50:38 +0000 Subject: [PATCH 19/22] Change message of sign up email --- django/admin_email/admin.py | 23 +++- .../templates/signup_mailing_list_email.html | 117 ++++-------------- 2 files changed, 44 insertions(+), 96 deletions(-) diff --git a/django/admin_email/admin.py b/django/admin_email/admin.py index 86e34b02..8e95da62 100644 --- a/django/admin_email/admin.py +++ b/django/admin_email/admin.py @@ -41,7 +41,6 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): if send_to_opt_in_users: context['unsubscribe_url'] = request.build_absolute_uri(f'/email/unsubscribe/{quote(recipient)}/') - # Render the email body using the selected template email_body_final = render_to_string(template_name, context) email = EmailMultiAlternatives( @@ -57,14 +56,14 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): try: email.send(fail_silently=False) - obj.status = 0 # Mark as sent + obj.status = 0 except Exception as e: - obj.status = 1 # Mark as failed + obj.status = 1 logger.exception(f"Failed to send email to {recipient}: {e}") super(OutgoingEmailAdmin, self).save_model(request, obj, form, change) def send_initial_sign_in_email(self, request, queryset): - # recipients are those who have not signedup yet + # recipients are those who have not signed up yet recipients = User.objects.filter(email_opt_in=False).values_list('email', flat=True) subject = "Stay Updated with Our Latest Analysis Features" @@ -72,7 +71,21 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): for recipient in recipients: context = { - 'email_body': "Welcome to our platform! We're excited to have you on board. Click the link below to join our mailing list and stay updated with our latest features and research papers.", + 'email_body': ( + "Hi,\n\n" + "We hope you're finding our nano-org website valuable for your research and data analysis needs. " + "We're excited to offer you the opportunity to join our mailing list, where you'll receive updates on " + "new analysis features, research papers, and other important developments.\n\n" + "By joining our mailing list, you'll be the first to know about:\n" + "• New analysis tools and features\n" + "• Latest research papers and publications\n" + "• Important announcements and updates\n\n" + "If you'd like to stay informed, simply click the link below to join our mailing list:\n\n" + "[Join Mailing List]\n\n" + "If you prefer not to receive these updates, no action is needed. You can also opt-out at any time by clicking the unsubscribe link in any of our emails.\n\n" + "Thank you for being a valued member of our community!\n\n" + "Best regards,\nHelen" + ), 'join_mailing_list_url': request.build_absolute_uri(f'/email/join_mailing_list/{quote(recipient)}/') } diff --git a/django/core/templates/signup_mailing_list_email.html b/django/core/templates/signup_mailing_list_email.html index 9681ff07..c2691411 100644 --- a/django/core/templates/signup_mailing_list_email.html +++ b/django/core/templates/signup_mailing_list_email.html @@ -1,99 +1,34 @@ <!DOCTYPE html> <html lang="en"> <head> - <title>mailing_list_email</title> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <title>Join Our Mailing List</title> <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1"> - <style> - body { - min-height: 100vh; - background: white; - font-family: 'Didact Gothic', sans-serif; - color: black; - display: grid; - grid-template-rows: auto 1fr auto; - } - .container { - width: 100%; - max-width: 600px; - margin: 0 auto; - padding: 20px; - background-color: #f9f9f9; - border: 1px solid #ddd; - border-radius: 5px; - } - .header, .footer { - background-color: #fdb44b; - color: black; - text-align: center; - padding: 10px 0; - } - .content { - margin-bottom: 20px; - } - .unsubscribe { - text-align: center; - margin-top: 20px; - } - .unsubscribe a { - color: #fdb44b; - text-decoration: none; - } - .unsubscribe a:hover { - text-decoration: underline; - } - a { - color: #714316; - text-decoration: underline; - } - a:hover { - color: #7d2604; - } - h1, h2 { - color: #995c38; - } - footer { - width: 100%; - height: auto; - line-height: 20px; - padding-top: 20px; - text-align: center; - color: #482b0e; - background-color: #fdb44b; - z-index: 1000; - } - footer a { - color: #4a3212; - } - </style> </head> <body> - <div class="container"> - <div class="content"> - {{ email_body|linebreaks }} - </div> - <div class="join_mailing_list"> - <a href="{{ join_mailing_list_url }}" style="display: inline-block; padding: 10px 20px; font-size: 16px; color: black; background-color: #fdb44b; text-decoration: none; border-radius: 5px;">Join our Mailing List</a> - </div> - <br> - <footer id="footer" role="contentinfo"> - <p> - nano-org website has been developed by the - <a href="https://www.birmingham.ac.uk/bear-software">Research Software Group</a> - at the - <a href="https://www.bham.ac.uk">University of Birmingham</a> - </p> - <p> - <a href="{% url 'general:user_agreement' %}">User Agreement</a> | - <a href="{% url 'django_contact_form' %}">Contact us</a> | - <a href="https://www.birmingham.ac.uk/privacy/index.aspx" target="_blank">Privacy</a> | - <a href="https://www.birmingham.ac.uk/legal/index.aspx" target="_blank">Legal</a> | - <a href="https://www.birmingham.ac.uk/university/governance/policies-regs/information/index.aspx" target="_blank">Freedom of Information</a> | - <a href="{% url 'general:cookies' %}">Cookies</a> | - <a href="https://accessibility.bear.bham.ac.uk/statements-nano-org.html" target="_blank">Accessibility</a> - </p> - </footer> + <div style="font-family: Arial, sans-serif; line-height: 1.5; max-width: 600px; margin: 0 auto; background: #f9f9f9; padding: 20px; border: 1px solid #ddd;"> + <p>Hi,</p> + <p> + We hope you're finding our nano-org website valuable for your research and data analysis needs. We're excited to offer you the opportunity to join our mailing list, where you'll receive updates on new analysis features, research papers, and other important developments. + </p> + <p> + By joining our mailing list, you'll be the first to know about: + </p> + <ul> + <li>New analysis tools and features</li> + <li>Latest research papers and publications</li> + <li>Important announcements and updates</li> + </ul> + <p> + If you'd like to stay informed, simply click the link below to join our mailing list: + </p> + <p style="text-align: center;"> + <a href="{{ join_mailing_list_url }}" style="padding: 10px 20px; background: #fdb44b; color: white; text-decoration: none; border-radius: 5px;">Join Mailing List</a> + </p> + <p> + If you prefer not to receive these updates, no action is needed. You can also opt-out at any time by clicking the unsubscribe link in any of our emails. + </p> + <p>Thank you for being a valued member of our community!</p> + <p>Best regards,<br>Helen</p> </div> </body> -</html> \ No newline at end of file +</html> -- GitLab From 3644ccc6a4d84c3c62d3144e1c9da09c1674183b Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Thu, 16 Jan 2025 15:49:13 +0000 Subject: [PATCH 20/22] fixing emails from option and link in plain text --- django/admin_email/admin.py | 43 ++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/django/admin_email/admin.py b/django/admin_email/admin.py index 8e95da62..0f71f1cf 100644 --- a/django/admin_email/admin.py +++ b/django/admin_email/admin.py @@ -70,8 +70,9 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): template_name = 'signup_mailing_list_email.html' for recipient in recipients: - context = { - 'email_body': ( + join_link = request.build_absolute_uri(f'/email/join_mailing_list/{quote(recipient)}/') + + email_body = ( "Hi,\n\n" "We hope you're finding our nano-org website valuable for your research and data analysis needs. " "We're excited to offer you the opportunity to join our mailing list, where you'll receive updates on " @@ -81,30 +82,32 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): "• Latest research papers and publications\n" "• Important announcements and updates\n\n" "If you'd like to stay informed, simply click the link below to join our mailing list:\n\n" - "[Join Mailing List]\n\n" + f"{join_link}\n\n" "If you prefer not to receive these updates, no action is needed. You can also opt-out at any time by clicking the unsubscribe link in any of our emails.\n\n" "Thank you for being a valued member of our community!\n\n" "Best regards,\nHelen" - ), - 'join_mailing_list_url': request.build_absolute_uri(f'/email/join_mailing_list/{quote(recipient)}/') - } + ) + context = { + 'email_body': email_body, + 'join_mailing_list_url': join_link, + } + email_body_final = render_to_string(template_name, context) + for obj in queryset: + email = EmailMultiAlternatives( + subject=subject, + body=email_body, + from_email=obj.from_email, + to=[recipient] + ) + email.attach_alternative(email_body_final, "text/html") # HTML version - email = EmailMultiAlternatives( - subject=subject, - body=context['email_body'], - from_email='your_email@example.com', - to=[recipient] - ) - - email.attach_alternative(email_body_final, "text/html") - - try: - email.send(fail_silently=False) - logger.info(f"Sent initial sign-in email to {recipient}") - except Exception as e: - logger.exception(f"Failed to send initial sign-in email to {recipient}: {e}") + try: + email.send(fail_silently=False) + logger.info(f"Sent initial sign-in email to {recipient}") + except Exception as e: + logger.exception(f"Failed to send initial sign-in email to {recipient}: {e}") self.message_user(request, "Initial sign-in emails have been sent.") -- GitLab From c23ee79c95aec61d88dfaf9221dc364bbbdad4f1 Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Fri, 17 Jan 2025 09:51:28 +0000 Subject: [PATCH 21/22] update nano account --- django-nano-account | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django-nano-account b/django-nano-account index f021203f..12546a6b 160000 --- a/django-nano-account +++ b/django-nano-account @@ -1 +1 @@ -Subproject commit f021203f5c814f6ac9d9d722194cef12bb5a9814 +Subproject commit 12546a6b749ac4d23e6732bcde4171cd498eb285 -- GitLab From e20b5080384c1ea01ab0c3570db4c9ff7afbfbae Mon Sep 17 00:00:00 2001 From: HelenAbbott <h.e.abbott.1@bham.ac.uk> Date: Fri, 17 Jan 2025 10:56:39 +0000 Subject: [PATCH 22/22] improve unsub button --- django/admin_email/admin.py | 6 +++--- django/core/templates/signup_mailing_list_email.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/django/admin_email/admin.py b/django/admin_email/admin.py index 0f71f1cf..9015de3d 100644 --- a/django/admin_email/admin.py +++ b/django/admin_email/admin.py @@ -56,9 +56,9 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): try: email.send(fail_silently=False) - obj.status = 0 + obj.status = 0 except Exception as e: - obj.status = 1 + obj.status = 1 logger.exception(f"Failed to send email to {recipient}: {e}") super(OutgoingEmailAdmin, self).save_model(request, obj, form, change) @@ -92,7 +92,7 @@ class CustomOutgoingEmailAdmin(OutgoingEmailAdmin): 'email_body': email_body, 'join_mailing_list_url': join_link, } - + email_body_final = render_to_string(template_name, context) for obj in queryset: email = EmailMultiAlternatives( diff --git a/django/core/templates/signup_mailing_list_email.html b/django/core/templates/signup_mailing_list_email.html index c2691411..c910661b 100644 --- a/django/core/templates/signup_mailing_list_email.html +++ b/django/core/templates/signup_mailing_list_email.html @@ -22,7 +22,7 @@ If you'd like to stay informed, simply click the link below to join our mailing list: </p> <p style="text-align: center;"> - <a href="{{ join_mailing_list_url }}" style="padding: 10px 20px; background: #fdb44b; color: white; text-decoration: none; border-radius: 5px;">Join Mailing List</a> + <a href="{{ join_mailing_list_url }}" style="padding: 10px 20px; background: #fdb44b; color: rgb(5, 5, 5); text-decoration: none; border-radius: 5px;">Join Mailing List</a> </p> <p> If you prefer not to receive these updates, no action is needed. You can also opt-out at any time by clicking the unsubscribe link in any of our emails. -- GitLab