fix(notification): fixed formatting and linting
This commit is contained in:
1 parent
466d4b868a
commit
63d63d5e5c
7 files changed
+57
-57
No files matched your search
@@ -4,8 +4,8 @@
|
||||
from src.admin import ( # noqa: F401
|
||||
clients,
|
||||
finance,
|
||||
notifications,
|
||||
productivity,
|
||||
projects,
|
||||
services,
|
||||
notifications,
|
||||
)
|
||||
@@ -1,7 +1,9 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from src.admin.base import admin_site
|
||||
from src.models.notifications import Notification
|
||||
|
||||
|
||||
@admin.register(Notification, site=admin_site)
|
||||
class NotificationAdmin(admin.ModelAdmin):
|
||||
list_display = ("recipient", "subject", "status", "sent_at", "created_at")
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from src.models.notifications import check_and_send_notifications
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Checks for upcoming deadlines and sends notifications.'
|
||||
help = "Checks for upcoming deadlines and sends notifications."
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.stdout.write("Checking for notifications...")
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
("src", "0002_alter_client_phone"),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"""generated with djinit"""
|
||||
|
||||
# Create your models here.
|
||||
from .notifications import Notification
|
||||
|
||||
from .notifications import Notification as Notification
|
||||
+32
-28
@@ -1,19 +1,20 @@
|
||||
from django.db import models
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.mail import send_mail
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
from src.models.base import TimeStampedModel
|
||||
from src.models.projects import Project, Milestone, Task
|
||||
from src.models.services import Service
|
||||
from src.models.finance import Invoice
|
||||
from src.models.productivity import TimeEntry
|
||||
from src.models.projects import Milestone, Project, Task
|
||||
from src.models.services import Service
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class Notification(TimeStampedModel):
|
||||
STATUS_CHOICES = [
|
||||
("PENDING", "Pending"),
|
||||
@@ -26,11 +27,13 @@ class Notification(TimeStampedModel):
|
||||
message = models.TextField()
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="PENDING")
|
||||
sent_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
|
||||
# Generic relation to link to the source object (Project, Invoice, etc.)
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True)
|
||||
content_type = models.ForeignKey(
|
||||
ContentType, on_delete=models.CASCADE, null=True, blank=True
|
||||
)
|
||||
object_id = models.PositiveIntegerField(null=True, blank=True)
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
content_object = GenericForeignKey("content_type", "object_id")
|
||||
|
||||
class Meta:
|
||||
ordering = ["-created_at"]
|
||||
@@ -62,9 +65,9 @@ def get_admin_emails():
|
||||
admins = getattr(settings, "ADMINS", [])
|
||||
if admins:
|
||||
return [email for name, email in admins]
|
||||
|
||||
|
||||
# Fallback to superusers
|
||||
return list(User.objects.filter(is_superuser=True).values_list('email', flat=True))
|
||||
return list(User.objects.filter(is_superuser=True).values_list("email", flat=True))
|
||||
|
||||
|
||||
def create_and_send(recipient, subject, message, obj=None):
|
||||
@@ -77,7 +80,7 @@ def create_and_send(recipient, subject, message, obj=None):
|
||||
subject=subject,
|
||||
content_type=ct,
|
||||
object_id=obj.id,
|
||||
created_at__date=timezone.localdate()
|
||||
created_at__date=timezone.localdate(),
|
||||
).exists()
|
||||
if already_sent:
|
||||
return
|
||||
@@ -100,7 +103,7 @@ def check_and_send_notifications():
|
||||
"""
|
||||
today = timezone.localdate()
|
||||
tomorrow = today + timezone.timedelta(days=1)
|
||||
|
||||
|
||||
admin_emails = get_admin_emails()
|
||||
if not admin_emails:
|
||||
print("Warning: No admin emails found.")
|
||||
@@ -115,16 +118,16 @@ def check_and_send_notifications():
|
||||
recipient=project.client.email,
|
||||
subject=f"Project Deadline Reminder: {project.name}",
|
||||
message=f"Dear {project.client.name},\n\nThe project '{project.name}' is due on {project.deadline}.\n\nRegards,\nDevSuite",
|
||||
obj=project
|
||||
obj=project,
|
||||
)
|
||||
|
||||
|
||||
# Notify Admin
|
||||
for email in admin_emails:
|
||||
create_and_send(
|
||||
recipient=email,
|
||||
subject=f"Admin Alert: Project Deadline - {project.name}",
|
||||
message=f"Project '{project.name}' for client {project.client.name} is due on {project.deadline}.",
|
||||
obj=project
|
||||
obj=project,
|
||||
)
|
||||
|
||||
# --- Milestones (Due Date) ---
|
||||
@@ -138,16 +141,16 @@ def check_and_send_notifications():
|
||||
recipient=project.client.email,
|
||||
subject=f"Milestone Due: {milestone.title}",
|
||||
message=f"Dear {project.client.name},\n\nThe milestone '{milestone.title}' for project '{project.name}' is due on {milestone.due_date}.\n\nRegards,\nDevSuite",
|
||||
obj=milestone
|
||||
obj=milestone,
|
||||
)
|
||||
|
||||
|
||||
# Notify Admin
|
||||
for email in admin_emails:
|
||||
create_and_send(
|
||||
recipient=email,
|
||||
subject=f"Admin Alert: Milestone Due - {milestone.title}",
|
||||
message=f"Milestone '{milestone.title}' (Project: {project.name}) is due on {milestone.due_date}.",
|
||||
obj=milestone
|
||||
obj=milestone,
|
||||
)
|
||||
|
||||
# --- Tasks (Due Date) ---
|
||||
@@ -159,7 +162,7 @@ def check_and_send_notifications():
|
||||
recipient=email,
|
||||
subject=f"Task Due: {task.title}",
|
||||
message=f"Task '{task.title}' (Project: {task.project.name}) is due on {task.due_date}.",
|
||||
obj=task
|
||||
obj=task,
|
||||
)
|
||||
|
||||
# --- Services (Expiry Date) ---
|
||||
@@ -171,33 +174,34 @@ def check_and_send_notifications():
|
||||
recipient=service.client.email,
|
||||
subject=f"Service Expiry Warning: {service.name}",
|
||||
message=f"Dear {service.client.name},\n\nYour service '{service.name}' ({service.get_service_type_display()}) expires on {service.expiry_date}.\nPlease renew it soon.\n\nRegards,\nDevSuite",
|
||||
obj=service
|
||||
obj=service,
|
||||
)
|
||||
|
||||
# --- Invoices (Due Date) ---
|
||||
# Notify Client
|
||||
invoices_due = Invoice.objects.filter(due_date=tomorrow, status__in=["SENT", "OVERDUE"])
|
||||
invoices_due = Invoice.objects.filter(
|
||||
due_date=tomorrow, status__in=["SENT", "OVERDUE"]
|
||||
)
|
||||
for invoice in invoices_due:
|
||||
if invoice.client.email:
|
||||
create_and_send(
|
||||
recipient=invoice.client.email,
|
||||
subject=f"Invoice Due: {invoice.invoice_number}",
|
||||
message=f"Dear {invoice.client.name},\n\nInvoice {invoice.invoice_number} for {invoice.amount} is due on {invoice.due_date}.\nPlease make payment.\n\nRegards,\nDevSuite",
|
||||
obj=invoice
|
||||
obj=invoice,
|
||||
)
|
||||
|
||||
# --- Productivity / TimeEntries ---
|
||||
# Notify Admin for recently completed time entries (last 24h)
|
||||
recent_entries = TimeEntry.objects.filter(
|
||||
end_time__gte=timezone.now() - timezone.timedelta(hours=24),
|
||||
end_time__lte=timezone.now()
|
||||
end_time__lte=timezone.now(),
|
||||
)
|
||||
|
||||
|
||||
for entry in recent_entries:
|
||||
# Check if we already sent a notification for this entry
|
||||
if Notification.objects.filter(
|
||||
content_type=ContentType.objects.get_for_model(entry),
|
||||
object_id=entry.id
|
||||
content_type=ContentType.objects.get_for_model(entry), object_id=entry.id
|
||||
).exists():
|
||||
continue
|
||||
|
||||
@@ -206,5 +210,5 @@ def check_and_send_notifications():
|
||||
recipient=email,
|
||||
subject=f"Time Entry Logged: {entry.description}",
|
||||
message=f"User logged time for '{entry.description}'.\nDuration: {entry.duration}\nEnd Time: {entry.end_time}",
|
||||
obj=entry
|
||||
obj=entry,
|
||||
)
|
||||
+18
-24
@@ -1,56 +1,50 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core import mail
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
from django.core import mail
|
||||
from django.contrib.auth import get_user_model
|
||||
from src.models.projects import Project
|
||||
|
||||
from src.models.clients import Client
|
||||
from src.models.notifications import check_and_send_notifications, Notification
|
||||
from src.models.notifications import Notification, check_and_send_notifications
|
||||
from src.models.projects import Project
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class NotificationTests(TestCase):
|
||||
def test_send_deadline_notifications(self):
|
||||
# Setup
|
||||
admin = User.objects.create_superuser('admin', 'admin@example.com', 'password')
|
||||
User.objects.create_superuser("admin", "admin@example.com", "password")
|
||||
client = Client.objects.create(name="Test Client", email="client@example.com")
|
||||
|
||||
|
||||
# Project due tomorrow
|
||||
tomorrow = timezone.localdate() + timezone.timedelta(days=1)
|
||||
project = Project.objects.create(
|
||||
name="Test Project",
|
||||
client=client,
|
||||
deadline=tomorrow
|
||||
)
|
||||
|
||||
Project.objects.create(name="Test Project", client=client, deadline=tomorrow)
|
||||
|
||||
# Run notification check
|
||||
check_and_send_notifications()
|
||||
|
||||
|
||||
# Verify emails sent
|
||||
# Expecting 2 emails: 1 to client, 1 to admin
|
||||
self.assertEqual(len(mail.outbox), 2)
|
||||
|
||||
|
||||
# Verify Notification objects created
|
||||
self.assertEqual(Notification.objects.count(), 2)
|
||||
|
||||
|
||||
client_notif = Notification.objects.get(recipient="client@example.com")
|
||||
self.assertIn("Project Deadline Reminder", client_notif.subject)
|
||||
|
||||
|
||||
admin_notif = Notification.objects.get(recipient="admin@example.com")
|
||||
self.assertIn("Admin Alert", admin_notif.subject)
|
||||
|
||||
def test_no_notifications_if_not_due(self):
|
||||
# Setup
|
||||
client = Client.objects.create(name="Test Client", email="client@example.com")
|
||||
|
||||
|
||||
# Project due day after tomorrow
|
||||
day_after = timezone.localdate() + timezone.timedelta(days=2)
|
||||
Project.objects.create(
|
||||
name="Future Project",
|
||||
client=client,
|
||||
deadline=day_after
|
||||
)
|
||||
|
||||
Project.objects.create(name="Future Project", client=client, deadline=day_after)
|
||||
|
||||
check_and_send_notifications()
|
||||
|
||||
|
||||
self.assertEqual(len(mail.outbox), 0)
|
||||
self.assertEqual(Notification.objects.count(), 0)
|
||||
Reference in new issue
Block a user