Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion alert_system/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.contrib import admin

from .models import Connector, ExtractionItem, LoadItem
from .models import AlertEmailLog, AlertEmailThread, Connector, ExtractionItem, LoadItem


@admin.register(Connector)
Expand Down Expand Up @@ -50,3 +50,45 @@ class LoadItemAdmin(admin.ModelAdmin):
"id",
"correlation_id",
)


@admin.register(AlertEmailThread)
class AlertEmailThreadAdmin(admin.ModelAdmin):
list_display = (
"user",
"correlation_id",
"root_email_message_id",
)
search_fields = (
"correlation_id",
"root_email_message_id",
"user__username",
)
list_select_related = ("user",)
autocomplete_fields = ("user",)


@admin.register(AlertEmailLog)
class AlertEmailLogAdmin(admin.ModelAdmin):
list_display = (
"id",
"message_id",
"status",
)
list_select_related = (
"user",
"subscription",
"item",
"thread",
)
search_fields = (
"user__username",
"message_id",
)
autocomplete_fields = (
"user",
"subscription",
"item",
"thread",
)
list_filter = ("status",)
75 changes: 75 additions & 0 deletions alert_system/dev_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from django.http import HttpResponse
from django.template import loader
from rest_framework import permissions
from rest_framework.views import APIView


class AlertEmailPreview(APIView):
permission_classes = [permissions.IsAuthenticated]

def get(self, request):
type_param = request.GET.get("type")

template_map = {
"alert": "email/alert_system/alert_notification.html",
"alert_reply": "email/alert_system/alert_notification_reply.html",
}

if type_param not in template_map:
valid_values = ", ".join(template_map.keys())
return HttpResponse(
f"Invalid 'type' parameter. Please use one of the following values: {valid_values}.",
)
context_map = {
"alert": {
"user_name": "Test User",
"event_title": "Test Title",
"event_description": "This is a test description for the alert email.",
"start_datetime": "2025-11-28 01:00:00",
"end_datetime": "2025-11-28 01:00:00",
"country_name": [
"Nepal",
],
"total_people_exposed": 1200,
"total_buildings_exposed": 150,
"hazard_types": "Flood",
"related_montandon_events": [
{
"event_title": "Related Event 1",
"total_people_exposed": 100,
"total_buildings_exposed": 300,
"start_datetime": "2025-11-28 01:00:00",
"end_datetime": "2025-11-28 01:00:00",
},
{
"event_title": "Related Event 2",
"total_people_exposed": 200,
"total_buildings_exposed": 500,
"start_datetime": "2025-11-28 01:00:00",
"end_datetime": "2025-11-28 01:00:00",
},
],
"related_go_events": [
"go-event-uuid-1",
"go-event-uuid-2",
],
},
"alert_reply": {
"event_title": "Test Title",
"event_description": "This is a test description for the alert email.",
"start_datetime": "2025-11-28 01:00:00",
"end_datetime": "2025-11-28 01:00:00",
"country_name": [
"Nepal",
],
"total_people_exposed": 1200,
"total_buildings_exposed": 150,
},
}

context = context_map.get(type_param)
if context is None:
return HttpResponse("No context found for the email preview.")
template_file = template_map[type_param]
template = loader.get_template(template_file)
return HttpResponse(template.render(context, request))
39 changes: 39 additions & 0 deletions alert_system/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import factory

from alert_system.models import AlertEmailLog, AlertEmailThread, Connector, LoadItem


class LoadItemFactory(factory.django.DjangoModelFactory):
class Meta:
model = LoadItem

@factory.post_generation
def related_montandon_events(self, create, extracted, **kwargs):
if not create:
return
if extracted:
for event in extracted:
self.related_montandon_events.add(event)

@factory.post_generation
def related_go_events(self, create, extracted, **kwargs):
if not create:
return
if extracted:
for event in extracted:
self.related_go_events.add(event)


class AlertEmailLogFactory(factory.django.DjangoModelFactory):
class Meta:
model = AlertEmailLog


class ConnectorFactory(factory.django.DjangoModelFactory):
class Meta:
model = Connector


class AlertEmailThreadFactory(factory.django.DjangoModelFactory):
class Meta:
model = AlertEmailThread
29 changes: 29 additions & 0 deletions alert_system/management/commands/alert_notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from django.core.management.base import BaseCommand

from alert_system.models import LoadItem
from alert_system.tasks import process_email_alert

# from sentry_sdk import monitor


# from main.sentry import SentryMonitor


class Command(BaseCommand):
help = "Send alert email notifications for eligible load items"

# @monitor(monitor_slug=SentryMonitor.ALERT_NOTIFICATION)
def handle(self, *args, **options):

items = LoadItem.objects.filter(item_eligible=True, is_past_event=False)

if not items.exists():
self.stdout.write(self.style.NOTICE("No eligible items found"))
return

self.stdout.write(self.style.NOTICE(f"Queueing {items.count()} items for alert email notification."))

for item in items.iterator():
process_email_alert.delay(load_item_id=item.id)

self.stdout.write(self.style.SUCCESS("All alert notification email queued successfully"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Generated by Django 4.2.26 on 2026-01-09 11:16

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('notifications', '0016_alertsubscription'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('alert_system', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='AlertEmailThread',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('correlation_id', models.CharField(help_text='Identifier linking related LoadItems into the same email thread.', max_length=255)),
('root_email_message_id', models.CharField(help_text='Message-ID of the first email in this thread.', max_length=255, unique=True)),
('root_message_sent_at', models.DateTimeField(help_text='Timestamp when the root email was sent.')),
('created_at', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='alert_email_threads', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Email Thread',
'verbose_name_plural': 'Email Threads',
'ordering': ['-id'],
},
),
migrations.CreateModel(
name='AlertEmailLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('message_id', models.CharField(help_text='Unique Message-ID of email for tracking and threading.', max_length=255, unique=True, verbose_name='Message ID')),
('status', models.IntegerField(choices=[(100, 'Pending'), (200, 'Processing'), (300, 'Sent'), (400, 'Failed')], db_index=True, default=100, verbose_name='Email Status')),
('email_sent_at', models.DateTimeField(blank=True, help_text='Timestamp when email was successfully sent.', null=True, verbose_name='Sent At')),
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='email_alert_load_item', to='alert_system.loaditem', verbose_name='Load Item')),
('subscription', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='email_alert_subscription', to='notifications.alertsubscription', verbose_name='Alert Subscription')),
('thread', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='email_alert_thread', to='alert_system.alertemailthread', verbose_name='Email Thread')),
('user', models.ForeignKey(help_text='The recipient of this alert email.', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'Email Alert Log',
'verbose_name_plural': 'Email Alert Logs',
'ordering': ['-id'],
},
),
migrations.AddIndex(
model_name='alertemailthread',
index=models.Index(fields=['correlation_id', 'user'], name='alert_syste_correla_2e9c7b_idx'),
),
migrations.AddIndex(
model_name='alertemaillog',
index=models.Index(fields=['user', 'subscription', 'email_sent_at'], name='alert_syste_user_id_06a2ee_idx'),
),
migrations.AddIndex(
model_name='alertemaillog',
index=models.Index(fields=['user', 'item', 'status'], name='alert_syste_user_id_e51594_idx'),
),
]
Loading
Loading