final all done 2nd last plan before goign live
This commit is contained in:
191
backend/igny8_core/business/notifications/models.py
Normal file
191
backend/igny8_core/business/notifications/models.py
Normal file
@@ -0,0 +1,191 @@
|
||||
"""
|
||||
Notification Models for IGNY8
|
||||
|
||||
This module provides a notification system for tracking AI operations,
|
||||
workflow events, and system alerts.
|
||||
"""
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from igny8_core.auth.models import AccountBaseModel
|
||||
|
||||
|
||||
class NotificationType(models.TextChoices):
|
||||
"""Notification type choices"""
|
||||
# AI Operations
|
||||
AI_CLUSTER_COMPLETE = 'ai_cluster_complete', 'Clustering Complete'
|
||||
AI_CLUSTER_FAILED = 'ai_cluster_failed', 'Clustering Failed'
|
||||
AI_IDEAS_COMPLETE = 'ai_ideas_complete', 'Ideas Generated'
|
||||
AI_IDEAS_FAILED = 'ai_ideas_failed', 'Idea Generation Failed'
|
||||
AI_CONTENT_COMPLETE = 'ai_content_complete', 'Content Generated'
|
||||
AI_CONTENT_FAILED = 'ai_content_failed', 'Content Generation Failed'
|
||||
AI_IMAGES_COMPLETE = 'ai_images_complete', 'Images Generated'
|
||||
AI_IMAGES_FAILED = 'ai_images_failed', 'Image Generation Failed'
|
||||
AI_PROMPTS_COMPLETE = 'ai_prompts_complete', 'Image Prompts Created'
|
||||
AI_PROMPTS_FAILED = 'ai_prompts_failed', 'Image Prompts Failed'
|
||||
|
||||
# Workflow
|
||||
CONTENT_READY_REVIEW = 'content_ready_review', 'Content Ready for Review'
|
||||
CONTENT_PUBLISHED = 'content_published', 'Content Published'
|
||||
CONTENT_PUBLISH_FAILED = 'content_publish_failed', 'Publishing Failed'
|
||||
|
||||
# WordPress Sync
|
||||
WORDPRESS_SYNC_SUCCESS = 'wordpress_sync_success', 'WordPress Sync Complete'
|
||||
WORDPRESS_SYNC_FAILED = 'wordpress_sync_failed', 'WordPress Sync Failed'
|
||||
|
||||
# Credits/Billing
|
||||
CREDITS_LOW = 'credits_low', 'Credits Running Low'
|
||||
CREDITS_DEPLETED = 'credits_depleted', 'Credits Depleted'
|
||||
|
||||
# Setup
|
||||
SITE_SETUP_COMPLETE = 'site_setup_complete', 'Site Setup Complete'
|
||||
KEYWORDS_IMPORTED = 'keywords_imported', 'Keywords Imported'
|
||||
|
||||
# System
|
||||
SYSTEM_INFO = 'system_info', 'System Information'
|
||||
|
||||
|
||||
class NotificationSeverity(models.TextChoices):
|
||||
"""Notification severity choices"""
|
||||
INFO = 'info', 'Info'
|
||||
SUCCESS = 'success', 'Success'
|
||||
WARNING = 'warning', 'Warning'
|
||||
ERROR = 'error', 'Error'
|
||||
|
||||
|
||||
class Notification(AccountBaseModel):
|
||||
"""
|
||||
Notification model for tracking events and alerts
|
||||
|
||||
Notifications are account-scoped (via AccountBaseModel) and can optionally target specific users.
|
||||
They support generic relations to link to any related object.
|
||||
"""
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='notifications',
|
||||
help_text='If null, notification is visible to all account users'
|
||||
)
|
||||
|
||||
# Notification content
|
||||
notification_type = models.CharField(
|
||||
max_length=50,
|
||||
choices=NotificationType.choices,
|
||||
default=NotificationType.SYSTEM_INFO
|
||||
)
|
||||
title = models.CharField(max_length=200)
|
||||
message = models.TextField()
|
||||
severity = models.CharField(
|
||||
max_length=20,
|
||||
choices=NotificationSeverity.choices,
|
||||
default=NotificationSeverity.INFO
|
||||
)
|
||||
|
||||
# Related site (optional)
|
||||
site = models.ForeignKey(
|
||||
'igny8_core_auth.Site',
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='notifications'
|
||||
)
|
||||
|
||||
# Generic relation to any object
|
||||
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')
|
||||
|
||||
# Action
|
||||
action_url = models.CharField(max_length=500, null=True, blank=True)
|
||||
action_label = models.CharField(max_length=50, null=True, blank=True)
|
||||
|
||||
# Status
|
||||
is_read = models.BooleanField(default=False)
|
||||
read_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
# Metadata for counts/details
|
||||
metadata = models.JSONField(default=dict, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['account', '-created_at']),
|
||||
models.Index(fields=['account', 'is_read', '-created_at']),
|
||||
models.Index(fields=['user', '-created_at']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.title} ({self.notification_type})"
|
||||
|
||||
def mark_as_read(self):
|
||||
"""Mark notification as read"""
|
||||
if not self.is_read:
|
||||
from django.utils import timezone
|
||||
self.is_read = True
|
||||
self.read_at = timezone.now()
|
||||
self.save(update_fields=['is_read', 'read_at', 'updated_at'])
|
||||
|
||||
@classmethod
|
||||
def create_notification(
|
||||
cls,
|
||||
account,
|
||||
notification_type: str,
|
||||
title: str,
|
||||
message: str,
|
||||
severity: str = NotificationSeverity.INFO,
|
||||
user=None,
|
||||
site=None,
|
||||
content_object=None,
|
||||
action_url: str = None,
|
||||
action_label: str = None,
|
||||
metadata: dict = None
|
||||
):
|
||||
"""
|
||||
Factory method to create notifications
|
||||
|
||||
Args:
|
||||
account: The account this notification belongs to
|
||||
notification_type: Type from NotificationType choices
|
||||
title: Notification title
|
||||
message: Notification message body
|
||||
severity: Severity level from NotificationSeverity choices
|
||||
user: Optional specific user (if None, visible to all account users)
|
||||
site: Optional related site
|
||||
content_object: Optional related object (using GenericForeignKey)
|
||||
action_url: Optional URL for action button
|
||||
action_label: Optional label for action button
|
||||
metadata: Optional dict with additional data (counts, etc.)
|
||||
|
||||
Returns:
|
||||
Created Notification instance
|
||||
"""
|
||||
notification = cls(
|
||||
account=account,
|
||||
user=user,
|
||||
notification_type=notification_type,
|
||||
title=title,
|
||||
message=message,
|
||||
severity=severity,
|
||||
site=site,
|
||||
action_url=action_url,
|
||||
action_label=action_label,
|
||||
metadata=metadata or {}
|
||||
)
|
||||
|
||||
if content_object:
|
||||
notification.content_type = ContentType.objects.get_for_model(content_object)
|
||||
notification.object_id = content_object.pk
|
||||
|
||||
notification.save()
|
||||
return notification
|
||||
Reference in New Issue
Block a user