From df5a0d76cc22b10865e3cc9b1e727ea6771c3369 Mon Sep 17 00:00:00 2001 From: uxairibrar Date: Tue, 11 Feb 2025 23:40:06 +0100 Subject: [PATCH 1/3] Send emails for new manuscripts 42 --- publications/admin.py | 48 ++++++++++++++++++- publications/migrations/0004_sentemaillog.py | 22 +++++++++ .../migrations/0005_sentemaillog_sent_by.py | 21 ++++++++ publications/models.py | 24 ++++++++++ publications/tasks.py | 41 ++++++++++++++++ tests/test_email.py | 39 +++++++++++++++ 6 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 publications/migrations/0004_sentemaillog.py create mode 100644 publications/migrations/0005_sentemaillog_sent_by.py create mode 100644 tests/test_email.py diff --git a/publications/admin.py b/publications/admin.py index 1afec7c..5d98eba 100644 --- a/publications/admin.py +++ b/publications/admin.py @@ -1,7 +1,14 @@ -from django.contrib import admin +from django.contrib import admin, messages from leaflet.admin import LeafletGeoAdmin from publications.models import Publication from import_export.admin import ImportExportModelAdmin +from django_q.tasks import schedule +from django.utils.timezone import now +from publications.models import SentEmailLog +from publications.tasks import send_monthly_email, schedule_monthly_email_task +from django_q.models import Schedule +from datetime import datetime, timedelta + @admin.action(description="Mark selected publications as published") def make_public(modeladmin, request, queryset): @@ -11,6 +18,38 @@ def make_public(modeladmin, request, queryset): def make_draft(modeladmin, request, queryset): queryset.update(status="d") +@admin.action(description="Send Monthly Manuscript Email") +def trigger_monthly_email(modeladmin, request, queryset): + """ + Admin action to trigger the email task manually. + """ + try: + send_monthly_email(sent_by=request.user) + messages.success(request, "Monthly manuscript email has been sent successfully.") + except Exception as e: + messages.error(request, f"Failed to send email: {e}") + +@admin.action(description="Schedule Monthly Email Task") +def trigger_monthly_email_task(modeladmin, request, queryset): + """ + Admin action to manually schedule the email task. + """ + try: + if not Schedule.objects.filter(func='publications.tasks.send_monthly_email').exists(): + next_run_date = datetime.now().replace(day=1) + timedelta(days=30) + + schedule( + 'publications.tasks.send_monthly_email', + schedule_type='M', + repeats=-1, + next_run=next_run_date + ) + messages.success(request, "Monthly email task has been scheduled successfully.") + else: + messages.warning(request, "The monthly email task is already scheduled.") + except Exception as e: + messages.error(request, f"Failed to schedule task: {e}") + @admin.register(Publication) class PublicationAdmin(LeafletGeoAdmin, ImportExportModelAdmin): """Publication Admin.""" @@ -18,3 +57,10 @@ class PublicationAdmin(LeafletGeoAdmin, ImportExportModelAdmin): list_display = ("doi", "creationDate", "lastUpdate", "created_by", "updated_by", "status", "provenance") actions = [make_public,make_draft] + +class SentEmailLogAdmin(admin.ModelAdmin): + list_display = ("recipient_email", "subject", "sent_at") + actions = [trigger_monthly_email,trigger_monthly_email_task] + + +admin.site.register(SentEmailLog, SentEmailLogAdmin) diff --git a/publications/migrations/0004_sentemaillog.py b/publications/migrations/0004_sentemaillog.py new file mode 100644 index 0000000..547dbe6 --- /dev/null +++ b/publications/migrations/0004_sentemaillog.py @@ -0,0 +1,22 @@ +# Generated by Django 4.0.5 on 2025-02-10 19:44 + +from django.db import migrations, models + +class Migration(migrations.Migration): + + dependencies = [ + ('publications', '0003_alter_publication_timeperiod_enddate_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='SentEmailLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('recipient_email', models.EmailField(max_length=254)), + ('subject', models.CharField(max_length=255)), + ('sent_at', models.DateTimeField(auto_now_add=True)), + ('email_content', models.TextField(blank=True, null=True)), + ], + ), + ] diff --git a/publications/migrations/0005_sentemaillog_sent_by.py b/publications/migrations/0005_sentemaillog_sent_by.py new file mode 100644 index 0000000..a4a17e7 --- /dev/null +++ b/publications/migrations/0005_sentemaillog_sent_by.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0.5 on 2025-02-11 20:56 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('publications', '0004_sentemaillog'), + ] + + operations = [ + migrations.AddField( + model_name='sentemaillog', + name='sent_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/publications/models.py b/publications/models.py index 520aaae..7eaf446 100644 --- a/publications/models.py +++ b/publications/models.py @@ -1,6 +1,10 @@ from django.contrib.gis.db import models from django.contrib.postgres.fields import ArrayField from django_currentuser.db.models import CurrentUserField +from django.utils.timezone import now +from django.contrib.auth import get_user_model + +User = get_user_model() STATUS_CHOICES = ( ("d", "Draft"), @@ -84,6 +88,26 @@ class Meta: ordering = ['user_name'] verbose_name = "subscription" +class SentEmailLog(models.Model): + recipient_email = models.EmailField() + subject = models.CharField(max_length=255) + sent_at = models.DateTimeField(auto_now_add=True) + email_content = models.TextField(blank=True, null=True) + sent_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) # ✅ Track who sent it + + def __str__(self): + return f"Email to {self.recipient_email} at {self.sent_at}" + + @classmethod + def log_email(cls, recipient, subject, content, sent_by=None): + cls.objects.create( + recipient_email=recipient, + subject=subject, + sent_at=now(), + email_content=content, + sent_by=sent_by + ) + # handle import/export relations, see https://django-import-export.readthedocs.io/en/stable/advanced_usage.html#creating-non-existent-relations from import_export import fields, resources from import_export.widgets import ForeignKeyWidget diff --git a/publications/tasks.py b/publications/tasks.py index c27f94e..5738e88 100644 --- a/publications/tasks.py +++ b/publications/tasks.py @@ -8,6 +8,12 @@ import xml.dom.minidom from django.contrib.gis.geos import GEOSGeometry import requests +from django.core.mail import send_mail +from django.conf import settings +from django.utils.timezone import now +from django.contrib.auth.models import User +from .models import SentEmailLog +from datetime import timedelta def extract_geometry_from_html(content): @@ -104,3 +110,38 @@ def harvest_oai_endpoint(url): parse_oai_xml_and_save_publications(response.content) except requests.exceptions.RequestException as e: print ("The requested URL is invalid or has bad connection.Please change the URL") + +def send_monthly_email(sent_by=None): + recipients = User.objects.values_list('email', flat=True) + last_month = now().replace(day=1) - timedelta(days=1) # Get last month's last day + new_manuscripts = Publication.objects.filter(creationDate__month=last_month.month) + + if not new_manuscripts.exists(): + print("No new manuscripts found for this month. Skipping email.") + return + + subject = "New Manuscripts This Month" + content = "Here are the new manuscripts:\n" + "\n".join([pub.title for pub in new_manuscripts]) + + for recipient in recipients: + print(f"Sending email to {recipient}") + try: + send_mail( + subject, + content, + settings.EMAIL_HOST_USER, + [recipient], + fail_silently=False, + ) + SentEmailLog.log_email(recipient, subject, content, sent_by=sent_by) + print(f"Email sent successfully to {recipient}") + except Exception as e: + print(f"Failed to send email to {recipient}: {e}") + +def schedule_monthly_email_task(): + schedule( + 'publications.tasks.send_monthly_email', + schedule_type='MONTHLY', + repeats=-1, + next_run=datetime.now().replace(day=28, hour=23, minute=59) + ) \ No newline at end of file diff --git a/tests/test_email.py b/tests/test_email.py new file mode 100644 index 0000000..8580232 --- /dev/null +++ b/tests/test_email.py @@ -0,0 +1,39 @@ +from django.test import TestCase +from django.core import mail +from publications.tasks import send_monthly_email +from publications.models import SentEmailLog, Publication +from django.utils.timezone import now +from django.contrib.auth.models import User + +class EmailIntegrationTest(TestCase): + def setUp(self): + """Setup test data before each test""" + self.user = User.objects.create_user(username="testuser", email="test@example.com", password="testpass") + + def test_send_monthly_email_with_publications(self): + """Test if the monthly email is sent when publications exist""" + Publication.objects.create(title="Test Manuscript", creationDate=now()) + self.assertEqual(len(mail.outbox), 0) + send_monthly_email(sent_by=self.user) + + self.assertEqual(len(mail.outbox), 1) + + sent_email = mail.outbox[0] + self.assertIn("New Manuscripts This Month", sent_email.subject) + self.assertIn("Test Manuscript", sent_email.body) + self.assertEqual(sent_email.to, ["test@example.com"]) + + self.assertTrue(SentEmailLog.objects.filter(recipient_email="test@example.com").exists()) + + email_log = SentEmailLog.objects.get(recipient_email="test@example.com") + self.assertEqual(email_log.sent_by, self.user) + + def test_send_monthly_email_without_publications(self): + """Test that no email is sent when no new publications exist""" + self.assertEqual(len(mail.outbox), 0) + + send_monthly_email(sent_by=self.user) + + self.assertEqual(len(mail.outbox), 0) + + self.assertFalse(SentEmailLog.objects.exists()) From 5f4814adcc0dec08b6d473118beae1e2768a9ac8 Mon Sep 17 00:00:00 2001 From: uxairibrar Date: Thu, 13 Mar 2025 03:21:03 +0100 Subject: [PATCH 2/3] Updated Sent Email Logic and PR updates --- optimap/settings.py | 1 + publications/admin.py | 44 ++-- publications/models.py | 31 ++- publications/signals.py | 12 +- publications/tasks.py | 54 +++-- publications/templates/user_settings.html | 282 ++++++++++++++++------ publications/views.py | 17 +- tests/test_email.py | 61 ++++- 8 files changed, 369 insertions(+), 133 deletions(-) diff --git a/optimap/settings.py b/optimap/settings.py index d252053..7180265 100644 --- a/optimap/settings.py +++ b/optimap/settings.py @@ -183,6 +183,7 @@ EMAIL_USE_TLS = env('OPTIMAP_EMAIL_USE_TLS', default=False) EMAIL_USE_SSL = env('OPTIMAP_EMAIL_USE_SSL', default=False) EMAIL_IMAP_SENT_FOLDER = env('OPTIMAP_EMAIL_IMAP_SENT_FOLDER', default='') +EMAIL_SEND_DELAY = 2 MIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', diff --git a/publications/admin.py b/publications/admin.py index 5d98eba..a00a778 100644 --- a/publications/admin.py +++ b/publications/admin.py @@ -4,7 +4,7 @@ from import_export.admin import ImportExportModelAdmin from django_q.tasks import schedule from django.utils.timezone import now -from publications.models import SentEmailLog +from publications.models import EmailLog, UserProfile from publications.tasks import send_monthly_email, schedule_monthly_email_task from django_q.models import Schedule from datetime import datetime, timedelta @@ -24,7 +24,7 @@ def trigger_monthly_email(modeladmin, request, queryset): Admin action to trigger the email task manually. """ try: - send_monthly_email(sent_by=request.user) + send_monthly_email(trigger_source='admin', sent_by=request.user) messages.success(request, "Monthly manuscript email has been sent successfully.") except Exception as e: messages.error(request, f"Failed to send email: {e}") @@ -34,19 +34,9 @@ def trigger_monthly_email_task(modeladmin, request, queryset): """ Admin action to manually schedule the email task. """ - try: - if not Schedule.objects.filter(func='publications.tasks.send_monthly_email').exists(): - next_run_date = datetime.now().replace(day=1) + timedelta(days=30) - - schedule( - 'publications.tasks.send_monthly_email', - schedule_type='M', - repeats=-1, - next_run=next_run_date - ) - messages.success(request, "Monthly email task has been scheduled successfully.") - else: - messages.warning(request, "The monthly email task is already scheduled.") + try: + schedule_monthly_email_task(sent_by=request.user) + messages.success(request, "Monthly email task has been scheduled successfully.") except Exception as e: messages.error(request, f"Failed to schedule task: {e}") @@ -58,9 +48,25 @@ class PublicationAdmin(LeafletGeoAdmin, ImportExportModelAdmin): actions = [make_public,make_draft] -class SentEmailLogAdmin(admin.ModelAdmin): - list_display = ("recipient_email", "subject", "sent_at") - actions = [trigger_monthly_email,trigger_monthly_email_task] +class EmailLogAdmin(admin.ModelAdmin): + list_display = ( + "recipient_email", + "subject", + "sent_at", + "sent_by", + "trigger_source", + "status", + "error_message", + ) + list_filter = ("status", "trigger_source", "sent_at") + search_fields = ("recipient_email", "subject", "sent_by__username") + actions = [trigger_monthly_email, trigger_monthly_email_task] + + +class UserProfileAdmin(admin.ModelAdmin): + list_display = ("user", "notify_new_manuscripts") + search_fields = ("user__email",) -admin.site.register(SentEmailLog, SentEmailLogAdmin) +admin.site.register(EmailLog, EmailLogAdmin) +admin.site.register(UserProfile, UserProfileAdmin) diff --git a/publications/models.py b/publications/models.py index 7eaf446..7a8cccd 100644 --- a/publications/models.py +++ b/publications/models.py @@ -88,24 +88,38 @@ class Meta: ordering = ['user_name'] verbose_name = "subscription" -class SentEmailLog(models.Model): +class EmailLog(models.Model): + TRIGGER_CHOICES = [ + ("admin", "Admin Panel"), + ("scheduled", "Scheduled Task"), + ("manual", "Manually Triggered"), + ] recipient_email = models.EmailField() subject = models.CharField(max_length=255) sent_at = models.DateTimeField(auto_now_add=True) email_content = models.TextField(blank=True, null=True) - sent_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) # ✅ Track who sent it + sent_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) + trigger_source = models.CharField(max_length=50, choices=TRIGGER_CHOICES, default="manual") + status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="success") + error_message = models.TextField(null=True, blank=True) def __str__(self): - return f"Email to {self.recipient_email} at {self.sent_at}" + sender = self.sent_by.email if self.sent_by else "System" + return f"Email to {self.recipient_email} by {sender} ({self.get_trigger_source_display()})" @classmethod - def log_email(cls, recipient, subject, content, sent_by=None): + def log_email(cls, recipient, subject, content, sent_by=None, trigger_source="manual", status="success", error_message=None): + """Logs the sent email, storing who triggered it and how it was sent.""" cls.objects.create( recipient_email=recipient, subject=subject, sent_at=now(), email_content=content, - sent_by=sent_by + sent_by=sent_by, + trigger_source=trigger_source, + status=status, + error_message=error_message, + ) # handle import/export relations, see https://django-import-export.readthedocs.io/en/stable/advanced_usage.html#creating-non-existent-relations @@ -128,3 +142,10 @@ class PublicationResource(resources.ModelResource): class Meta: model = Publication fields = ('created_by','updated_by',) + +class UserProfile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + notify_new_manuscripts = models.BooleanField(default=False) + + def __str__(self): + return f"{self.user.username} - Notifications: {self.notify_new_manuscripts}" diff --git a/publications/signals.py b/publications/signals.py index d54655d..3041364 100644 --- a/publications/signals.py +++ b/publications/signals.py @@ -3,8 +3,11 @@ from django.db.models.signals import pre_save from django.dispatch import receiver -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.conf import settings +from django.db.models.signals import post_save +User = get_user_model() +from publications.models import UserProfile @receiver(pre_save, sender=User) def update_user_callback(sender, instance, **kwargs): @@ -14,3 +17,10 @@ def update_user_callback(sender, instance, **kwargs): logging.warning('Registering user %s as admin', instance.email) instance.is_staff = True instance.is_superuser = True + +@receiver(post_save, sender=User) +def create_or_update_user_profile(sender, instance, created, **kwargs): + if created: + UserProfile.objects.create(user=instance) + else: + instance.userprofile.save() diff --git a/publications/tasks.py b/publications/tasks.py index 5738e88..2374f1b 100644 --- a/publications/tasks.py +++ b/publications/tasks.py @@ -12,8 +12,12 @@ from django.conf import settings from django.utils.timezone import now from django.contrib.auth.models import User -from .models import SentEmailLog -from datetime import timedelta +from .models import EmailLog +from datetime import datetime, timedelta +from django_q.tasks import schedule +from django_q.models import Schedule +import time +import calendar def extract_geometry_from_html(content): @@ -111,20 +115,18 @@ def harvest_oai_endpoint(url): except requests.exceptions.RequestException as e: print ("The requested URL is invalid or has bad connection.Please change the URL") -def send_monthly_email(sent_by=None): - recipients = User.objects.values_list('email', flat=True) - last_month = now().replace(day=1) - timedelta(days=1) # Get last month's last day +def send_monthly_email(trigger_source='manual', sent_by=None): + recipients = User.objects.filter(userprofile__notify_new_manuscripts=True).values_list('email', flat=True) + last_month = now().replace(day=1) - timedelta(days=1) new_manuscripts = Publication.objects.filter(creationDate__month=last_month.month) - if not new_manuscripts.exists(): - print("No new manuscripts found for this month. Skipping email.") + if not recipients.exists() or not new_manuscripts.exists(): return subject = "New Manuscripts This Month" content = "Here are the new manuscripts:\n" + "\n".join([pub.title for pub in new_manuscripts]) for recipient in recipients: - print(f"Sending email to {recipient}") try: send_mail( subject, @@ -133,15 +135,29 @@ def send_monthly_email(sent_by=None): [recipient], fail_silently=False, ) - SentEmailLog.log_email(recipient, subject, content, sent_by=sent_by) - print(f"Email sent successfully to {recipient}") + + EmailLog.log_email( + recipient, subject, content, sent_by=sent_by, trigger_source=trigger_source, status="success" + ) + time.sleep(getattr(settings, "EMAIL_SEND_DELAY", 2)) + except Exception as e: - print(f"Failed to send email to {recipient}: {e}") - -def schedule_monthly_email_task(): - schedule( - 'publications.tasks.send_monthly_email', - schedule_type='MONTHLY', - repeats=-1, - next_run=datetime.now().replace(day=28, hour=23, minute=59) - ) \ No newline at end of file + error_message = str(e) + EmailLog.log_email( + recipient, subject, content, sent_by=sent_by, trigger_source=trigger_source, status="failed", error_message=error_message + ) + + +def schedule_monthly_email_task(sent_by=None): + if not Schedule.objects.filter(func='publications.tasks.send_monthly_email').exists(): + now = datetime.now() + last_day_of_month = calendar.monthrange(now.year, now.month)[1] # Get last day of the month + + next_run_date = now.replace(day=last_day_of_month, hour=23, minute=59) # Run at the end of the last day + schedule( + 'publications.tasks.send_monthly_email', + schedule_type='M', + repeats=-1, + next_run=next_run_date, + kwargs={'trigger_source': 'scheduled', 'sent_by': sent_by.id if sent_by else None} + ) diff --git a/publications/templates/user_settings.html b/publications/templates/user_settings.html index 2636c33..a808ced 100644 --- a/publications/templates/user_settings.html +++ b/publications/templates/user_settings.html @@ -1,94 +1,213 @@ -{% extends "base.html" %} -{% load static %} - -{% block navbar %} +{% extends "base.html" %} {% load static %} {% block navbar %} -{% endblock %} +{% endblock %} {% block content %} +
+
+
+
+
+

+ +

+
+
+
+
+ {% csrf_token %} + +
+ - {% block content %} -
-
+
+ + + +
+
-
-
-
-

- -

+
+
+
-
-
-
- {% csrf_token %} -
- -
- -
-
-
- +
+
+

+ +

+
+ +
+
+ + {% csrf_token %} +
+ +
+
-
- -
- -
+
+
+ +
+
+ +
+
- -
+
+
+
-
-
-

- -

-
-
-
- -

Deleting account is permenant. It cannot be reversed.

+
+
+

+ +

+
+
+
+

Deleting account is permenant. It cannot be reversed.

- + - @@ -98,4 +217,17 @@
- {% endblock %} \ No newline at end of file +
+ + +{% endblock %} diff --git a/publications/views.py b/publications/views.py index 45c671d..711626e 100644 --- a/publications/views.py +++ b/publications/views.py @@ -3,7 +3,7 @@ from django.contrib.auth.models import User from django.contrib.auth import login -from django.shortcuts import render +from django.shortcuts import render, redirect from django.core.cache import cache from django.http.request import HttpRequest from django.http import HttpResponseRedirect @@ -22,7 +22,9 @@ import time from math import floor from django_currentuser.middleware import (get_current_user, get_current_authenticated_user) - +from publications.models import UserProfile +from django.views.decorators.cache import never_cache +from django.urls import reverse LOGIN_TOKEN_LENGTH = 32 LOGIN_TOKEN_TIMEOUT_SECONDS = 10 * 60 @@ -120,8 +122,17 @@ def customlogout(request): messages.info(request, "You have successfully logged out.") return render(request, "logout.html") +@never_cache def user_settings(request): - return render(request,'user_settings.html') + profile, created = UserProfile.objects.get_or_create(user=request.user) + + if request.method == "POST": + profile.notify_new_manuscripts = request.POST.get("notify_new_manuscripts") == "on" + profile.save() + + return redirect(reverse("optimap:usersettings")) + + return render(request, "user_settings.html", {"profile": profile}) def user_subscriptions(request): if request.user.is_authenticated: diff --git a/tests/test_email.py b/tests/test_email.py index 8580232..2b19d0f 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -1,39 +1,78 @@ -from django.test import TestCase +import django +import os + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "optimap.settings") +django.setup() + +from django.test import TestCase, override_settings from django.core import mail from publications.tasks import send_monthly_email -from publications.models import SentEmailLog, Publication +from publications.models import EmailLog, Publication, UserProfile from django.utils.timezone import now from django.contrib.auth.models import User +from datetime import timedelta +from datetime import datetime +from django.contrib.gis.geos import Point, LineString, Polygon, GeometryCollection +@override_settings(EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend') class EmailIntegrationTest(TestCase): def setUp(self): """Setup test data before each test""" - self.user = User.objects.create_user(username="testuser", email="test@example.com", password="testpass") + Publication.objects.all().delete() + EmailLog.objects.all().delete() + User.objects.all().delete() + + self.user = User.objects.create_user(username="testuser1", email="test@example.com", password="testpass") + self.user_profile = UserProfile.objects.get(user=self.user) + + self.user_profile.notify_new_manuscripts = True + self.user_profile.save() def test_send_monthly_email_with_publications(self): """Test if the monthly email is sent when publications exist""" - Publication.objects.create(title="Test Manuscript", creationDate=now()) + + last_month = now().replace(day=1) - timedelta(days=1) + publication = Publication.objects.create( + title="Point Test", + abstract="Publication with a single point inside a collection.", + url="https://example.com/point", + status="p", + publicationDate=last_month, + doi="10.1234/test-doi-1", + geometry=GeometryCollection(Point(12.4924, 41.8902)), + ) + + Publication.objects.filter(id=publication.id).update(creationDate=last_month) + + publication.refresh_from_db() + self.assertEqual(len(mail.outbox), 0) + send_monthly_email(sent_by=self.user) self.assertEqual(len(mail.outbox), 1) sent_email = mail.outbox[0] - self.assertIn("New Manuscripts This Month", sent_email.subject) - self.assertIn("Test Manuscript", sent_email.body) - self.assertEqual(sent_email.to, ["test@example.com"]) - self.assertTrue(SentEmailLog.objects.filter(recipient_email="test@example.com").exists()) + self.assertIn(publication.title, sent_email.body) - email_log = SentEmailLog.objects.get(recipient_email="test@example.com") + self.assertEqual(sent_email.to, ["test@example.com"]) + + email_log = EmailLog.objects.latest('sent_at') + self.assertEqual(email_log.recipient_email, "test@example.com") self.assertEqual(email_log.sent_by, self.user) + def test_send_monthly_email_without_publications(self): """Test that no email is sent when no new publications exist""" + self.assertEqual(len(mail.outbox), 0) - send_monthly_email(sent_by=self.user) + send_monthly_email(sent_by=self.user) self.assertEqual(len(mail.outbox), 0) - self.assertFalse(SentEmailLog.objects.exists()) + self.assertFalse(EmailLog.objects.exists()) + + + From 21e92d7fd5b31b546038a8b836afff37ed2e3abe Mon Sep 17 00:00:00 2001 From: uxairibrar Date: Thu, 13 Mar 2025 10:00:31 +0100 Subject: [PATCH 3/3] Updated Readme and Updated settings.py --- README.md | 75 ++++++++++++++++++++++++--------------------- optimap/settings.py | 2 +- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index fe3a28e..937ae1b 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,18 @@ deactivate docker stop optimapDB ``` +#### Debug Mode Configuration + +By default, `OPTIMAP_DEBUG` is now set to `False` to ensure a secure and stable production environment. If you need to enable debug mode for development purposes, explicitly set the environment variable in your `.env` file or pass it as an argument when running the server. + +#### Enable Debug Mode for Development + +To enable debug mode, add the following to your `.env` file: + +```env +OPTIMAP_DEBUG=True +``` + ### Debug with VS Code Select the Python interpreter created above (`optimap` environment), see instructions at and . @@ -111,24 +123,22 @@ Configuration for debugging with VS Code: ```json { - "version": "0.2.0", - "configurations": [ - { - "name": "Python: Django Run", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/manage.py", - "args": [ - "runserver" - ], - "env": { - "OPTIMAP_DEBUG": "True", - "OPTIMAP_CACHE": "dummy" - }, - "django": true, - "justMyCode": true - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Django Run", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/manage.py", + "args": ["runserver"], + "env": { + "OPTIMAP_DEBUG": "True", + "OPTIMAP_CACHE": "dummy" + }, + "django": true, + "justMyCode": true + } + ] } ``` @@ -149,7 +159,7 @@ OPTIMAP_EMAIL_PORT=5587 ### Create superusers/admin -Superusers/admin can be created using the createsuperuser command: +Superusers/admin can be created using the createsuperuser command: ```bash python manage.py createsuperuser --username=optimap --email=nomail@optimap.science @@ -194,22 +204,17 @@ A configuration to debug the test code and also print deprecation warnings: ```json { - "name": "Python: Django Test", - "type": "python", - "request": "launch", - "pythonArgs": [ - "-Wa" - ], - "program": "${workspaceFolder}/manage.py", - "args": [ - "test", - "tests" - ], - "env": { - "OPTIMAP_DEBUG": "True" - }, - "django": true, - "justMyCode": true + "name": "Python: Django Test", + "type": "python", + "request": "launch", + "pythonArgs": ["-Wa"], + "program": "${workspaceFolder}/manage.py", + "args": ["test", "tests"], + "env": { + "OPTIMAP_DEBUG": "True" + }, + "django": true, + "justMyCode": true } ``` diff --git a/optimap/settings.py b/optimap/settings.py index 7180265..e307b36 100644 --- a/optimap/settings.py +++ b/optimap/settings.py @@ -35,7 +35,7 @@ SECRET_KEY = env('SECRET_KEY', default='django-insecure') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = env('OPTIMAP_DEBUG', default=True) +DEBUG = env('OPTIMAP_DEBUG', default=False) ALLOWED_HOSTS = [i.strip('[]') for i in env('OPTIMAP_ALLOWED_HOST', default='*').split(',')]