701 lines
25 KiB
Python
701 lines
25 KiB
Python
"""
|
|
Notification Settings Views
|
|
|
|
Provides admin interface for configuring notification preferences.
|
|
"""
|
|
|
|
from django.contrib import messages
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.core.paginator import Paginator
|
|
from django.shortcuts import render, redirect, get_object_or_404
|
|
from django.http import JsonResponse
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.views.decorators.http import require_POST
|
|
|
|
from apps.organizations.models import Hospital
|
|
from .settings_models import HospitalNotificationSettings, NotificationSettingsLog
|
|
|
|
|
|
def can_manage_notifications(user):
|
|
"""Check if user can manage notification settings"""
|
|
if user.is_superuser:
|
|
return True
|
|
if hasattr(user, "is_px_admin") and user.is_px_admin():
|
|
return True
|
|
if hasattr(user, "is_hospital_admin") and user.is_hospital_admin():
|
|
return True
|
|
return False
|
|
|
|
|
|
@login_required
|
|
def notification_settings_view(request, hospital_id=None):
|
|
"""
|
|
Notification settings configuration page.
|
|
|
|
Allows hospital admins to toggle notification preferences
|
|
for different events and channels.
|
|
"""
|
|
# Check permission
|
|
if not can_manage_notifications(request.user):
|
|
raise PermissionDenied("You do not have permission to manage notification settings.")
|
|
|
|
# Get hospital - if superuser, can view any; otherwise only their hospital
|
|
if request.user.is_superuser and hospital_id:
|
|
hospital = get_object_or_404(Hospital, id=hospital_id)
|
|
else:
|
|
hospital = request.user.hospital
|
|
hospital_id = hospital.id
|
|
|
|
# Get or create settings
|
|
settings = HospitalNotificationSettings.get_for_hospital(hospital_id)
|
|
|
|
# Group settings by category for display
|
|
notification_categories = [
|
|
{
|
|
"key": "complaint",
|
|
"name": "Complaint Notifications",
|
|
"icon": "bi-exclamation-triangle",
|
|
"description": "Notifications related to complaint lifecycle",
|
|
"events": [
|
|
{
|
|
"key": "complaint_acknowledgment",
|
|
"name": "Complaint Acknowledgment",
|
|
"description": "Sent to patient when complaint is acknowledged",
|
|
"icon": "bi-check-circle",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
{
|
|
"key": "complaint_assigned",
|
|
"name": "Complaint Assigned",
|
|
"description": "Sent to staff when complaint is assigned to them",
|
|
"icon": "bi-person-check",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
{
|
|
"key": "complaint_status_changed",
|
|
"name": "Complaint Status Changed",
|
|
"description": "Sent when complaint status is updated",
|
|
"icon": "bi-arrow-repeat",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
{
|
|
"key": "complaint_resolved",
|
|
"name": "Complaint Resolved",
|
|
"description": "Sent to patient when complaint is resolved",
|
|
"icon": "bi-check2-all",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
{
|
|
"key": "complaint_closed",
|
|
"name": "Complaint Closed",
|
|
"description": "Sent to patient when complaint is closed",
|
|
"icon": "bi-x-circle",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"key": "explanation",
|
|
"name": "Explanation Notifications",
|
|
"icon": "bi-chat-left-text",
|
|
"description": "Notifications for staff explanation workflow",
|
|
"events": [
|
|
{
|
|
"key": "explanation_requested",
|
|
"name": "Explanation Requested",
|
|
"description": "Sent to staff when explanation is requested",
|
|
"icon": "bi-question-circle",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
{
|
|
"key": "explanation_reminder",
|
|
"name": "Explanation Reminder (24h)",
|
|
"description": "Reminder sent 24h before SLA deadline",
|
|
"icon": "bi-clock-history",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
{
|
|
"key": "explanation_overdue",
|
|
"name": "Explanation Overdue/Escalation",
|
|
"description": "Sent to manager when explanation is overdue",
|
|
"icon": "bi-exclamation-diamond",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
{
|
|
"key": "explanation_received",
|
|
"name": "Explanation Received",
|
|
"description": "Sent to assignee when staff submits explanation",
|
|
"icon": "bi-inbox",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
],
|
|
"extra_settings": [
|
|
{
|
|
"key": "explanation_manager_cc",
|
|
"name": "Manager CC on Explanation Request",
|
|
"description": "CC manager when explanation is requested from staff",
|
|
"icon": "bi-person-badge",
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"key": "survey",
|
|
"name": "Survey Notifications",
|
|
"icon": "bi-clipboard-check",
|
|
"description": "Notifications for patient surveys",
|
|
"events": [
|
|
{
|
|
"key": "survey_invitation",
|
|
"name": "Survey Invitation",
|
|
"description": "Initial survey invitation sent to patient",
|
|
"icon": "bi-envelope-open",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
{
|
|
"key": "survey_reminder",
|
|
"name": "Survey Reminder",
|
|
"description": "Reminder for patients to complete survey",
|
|
"icon": "bi-bell",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
{
|
|
"key": "survey_completed",
|
|
"name": "Survey Completed",
|
|
"description": "Sent to admin when survey is completed",
|
|
"icon": "bi-check-circle-fill",
|
|
"channels": ["email", "sms"],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"key": "action",
|
|
"name": "Action Plan Notifications",
|
|
"icon": "bi-list-check",
|
|
"description": "Notifications for action plan assignments",
|
|
"events": [
|
|
{
|
|
"key": "action_assigned",
|
|
"name": "Action Assigned",
|
|
"description": "Sent to staff when action is assigned",
|
|
"icon": "bi-person-plus",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
{
|
|
"key": "action_due_soon",
|
|
"name": "Action Due Soon",
|
|
"description": "Reminder when action is approaching deadline",
|
|
"icon": "bi-calendar-event",
|
|
"channels": ["email", "sms"],
|
|
},
|
|
{
|
|
"key": "action_overdue",
|
|
"name": "Action Overdue",
|
|
"description": "Alert when action is past deadline",
|
|
"icon": "bi-calendar-x",
|
|
"channels": ["email", "sms"],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"key": "sla",
|
|
"name": "SLA Notifications",
|
|
"icon": "bi-stopwatch",
|
|
"description": "Notifications for SLA monitoring",
|
|
"events": [
|
|
{
|
|
"key": "sla_reminder",
|
|
"name": "SLA Reminder",
|
|
"description": "Reminder before SLA breach",
|
|
"icon": "bi-clock",
|
|
"channels": ["email", "sms"],
|
|
},
|
|
{
|
|
"key": "sla_breach",
|
|
"name": "SLA Breach Alert",
|
|
"description": "Alert when SLA is breached",
|
|
"icon": "bi-exclamation-octagon",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"key": "onboarding",
|
|
"name": "Onboarding Notifications",
|
|
"icon": "bi-person-plus",
|
|
"description": "Notifications for user onboarding and acknowledgements",
|
|
"events": [
|
|
{
|
|
"key": "onboarding_invitation",
|
|
"name": "Onboarding Invitation",
|
|
"description": "Sent to new provisional users to complete registration",
|
|
"icon": "bi-envelope-plus",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
{
|
|
"key": "onboarding_reminder",
|
|
"name": "Onboarding Reminder",
|
|
"description": "Reminder to complete onboarding before invitation expires",
|
|
"icon": "bi-bell",
|
|
"channels": ["email", "sms", "whatsapp"],
|
|
},
|
|
{
|
|
"key": "onboarding_completion",
|
|
"name": "Onboarding Completion",
|
|
"description": "Sent to admins when user completes onboarding",
|
|
"icon": "bi-check-circle-fill",
|
|
"channels": ["email", "sms"],
|
|
},
|
|
],
|
|
},
|
|
]
|
|
|
|
# Get recent change logs
|
|
change_logs = (
|
|
NotificationSettingsLog.objects.filter(hospital=hospital)
|
|
.select_related("changed_by")
|
|
.order_by("-created_at")[:10]
|
|
)
|
|
|
|
context = {
|
|
"hospital": hospital,
|
|
"settings": settings,
|
|
"categories": notification_categories,
|
|
"change_logs": change_logs,
|
|
"is_superuser": request.user.is_superuser,
|
|
}
|
|
|
|
return render(request, "notifications/settings.html", context)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def notification_settings_update(request, hospital_id=None):
|
|
"""
|
|
Update notification settings via AJAX or form POST.
|
|
"""
|
|
# Check permission
|
|
if not can_manage_notifications(request.user):
|
|
raise PermissionDenied("You do not have permission to manage notification settings.")
|
|
|
|
# Get hospital
|
|
if request.user.is_superuser and hospital_id:
|
|
hospital = get_object_or_404(Hospital, id=hospital_id)
|
|
else:
|
|
hospital = request.user.hospital
|
|
|
|
settings = HospitalNotificationSettings.get_for_hospital(hospital.id)
|
|
|
|
# Handle master switch
|
|
if "notifications_enabled" in request.POST:
|
|
old_value = settings.notifications_enabled
|
|
new_value = request.POST.get("notifications_enabled") == "on"
|
|
settings.notifications_enabled = new_value
|
|
settings.save()
|
|
|
|
NotificationSettingsLog.objects.create(
|
|
hospital=hospital,
|
|
changed_by=request.user,
|
|
field_name="notifications_enabled",
|
|
old_value=old_value,
|
|
new_value=new_value,
|
|
)
|
|
|
|
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
|
return JsonResponse({"success": True, "message": f"Notifications {'enabled' if new_value else 'disabled'}"})
|
|
messages.success(request, f"Notifications {'enabled' if new_value else 'disabled'}")
|
|
return redirect("notifications:settings_with_hospital", hospital_id=hospital.id)
|
|
|
|
# Handle individual toggle
|
|
field_name = request.POST.get("field")
|
|
value = request.POST.get("value") == "true"
|
|
|
|
if field_name and hasattr(settings, field_name):
|
|
old_value = getattr(settings, field_name)
|
|
setattr(settings, field_name, value)
|
|
settings.save()
|
|
|
|
# Log the change
|
|
NotificationSettingsLog.objects.create(
|
|
hospital=hospital, changed_by=request.user, field_name=field_name, old_value=old_value, new_value=value
|
|
)
|
|
|
|
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
|
return JsonResponse({"success": True, "field": field_name, "value": value})
|
|
|
|
messages.success(request, "Setting updated successfully")
|
|
else:
|
|
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
|
return JsonResponse({"success": False, "error": "Invalid field name"}, status=400)
|
|
messages.error(request, "Invalid setting")
|
|
|
|
return redirect("notifications:settings_with_hospital", hospital_id=hospital.id)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def update_quiet_hours(request, hospital_id=None):
|
|
"""
|
|
Update quiet hours settings.
|
|
"""
|
|
# Check permission
|
|
if not can_manage_notifications(request.user):
|
|
raise PermissionDenied("You do not have permission to manage notification settings.")
|
|
|
|
if request.user.is_superuser and hospital_id:
|
|
hospital = get_object_or_404(Hospital, id=hospital_id)
|
|
else:
|
|
hospital = request.user.hospital
|
|
|
|
settings = HospitalNotificationSettings.get_for_hospital(hospital.id)
|
|
|
|
settings.quiet_hours_enabled = request.POST.get("quiet_hours_enabled") == "on"
|
|
settings.quiet_hours_start = request.POST.get("quiet_hours_start", "22:00")
|
|
settings.quiet_hours_end = request.POST.get("quiet_hours_end", "08:00")
|
|
settings.save()
|
|
|
|
messages.success(request, "Quiet hours settings updated")
|
|
return redirect("notifications:settings_with_hospital", hospital_id=hospital.id)
|
|
|
|
|
|
@login_required
|
|
def test_notification(request, hospital_id=None):
|
|
"""
|
|
Send a test notification to verify settings.
|
|
"""
|
|
# Check permission
|
|
if not can_manage_notifications(request.user):
|
|
raise PermissionDenied("You do not have permission to manage notification settings.")
|
|
|
|
if request.user.is_superuser and hospital_id:
|
|
hospital = get_object_or_404(Hospital, id=hospital_id)
|
|
else:
|
|
hospital = request.user.hospital
|
|
|
|
settings = HospitalNotificationSettings.get_for_hospital(hospital.id)
|
|
channel = request.POST.get("channel", "email")
|
|
|
|
from .services import NotificationService
|
|
|
|
if channel == "email" and request.user.email:
|
|
NotificationService.send_email(
|
|
email=request.user.email,
|
|
subject="PX360 Test Notification",
|
|
message="This is a test notification from PX360.\n\nIf you received this, your email notifications are working correctly.",
|
|
metadata={"test": True, "hospital_id": str(hospital.id)},
|
|
)
|
|
messages.success(request, f"Test email sent to {request.user.email}")
|
|
|
|
elif channel == "sms":
|
|
phone = request.POST.get("test_phone")
|
|
if phone:
|
|
NotificationService.send_sms(
|
|
phone=phone,
|
|
message="PX360 Test: Your SMS notifications are configured correctly.",
|
|
metadata={"test": True, "hospital_id": str(hospital.id)},
|
|
)
|
|
messages.success(request, f"Test SMS sent to {phone}")
|
|
else:
|
|
messages.error(request, "Please provide a phone number")
|
|
|
|
else:
|
|
messages.error(request, "Invalid channel selected")
|
|
|
|
return redirect("notifications:settings_with_hospital", hospital_id=hospital.id)
|
|
|
|
|
|
@login_required
|
|
def notification_settings_api(request, hospital_id=None):
|
|
"""
|
|
API endpoint to get current notification settings as JSON.
|
|
Useful for AJAX updates and mobile apps.
|
|
"""
|
|
if request.user.is_superuser and hospital_id:
|
|
hospital = get_object_or_404(Hospital, id=hospital_id)
|
|
else:
|
|
hospital = request.user.hospital
|
|
|
|
settings = HospitalNotificationSettings.get_for_hospital(hospital.id)
|
|
|
|
# Build response with all settings
|
|
settings_dict = {}
|
|
for field in settings._meta.fields:
|
|
if field.name not in ["id", "uuid", "created_at", "updated_at", "hospital"]:
|
|
settings_dict[field.name] = getattr(settings, field.name)
|
|
|
|
return JsonResponse({"hospital_id": str(hospital.id), "hospital_name": hospital.name, "settings": settings_dict})
|
|
|
|
|
|
@login_required
|
|
def send_sms_direct(request):
|
|
"""
|
|
Direct SMS sending page for admins.
|
|
|
|
Allows PX Admins and Hospital Admins to send SMS messages
|
|
directly to any phone number.
|
|
"""
|
|
from .services import NotificationService
|
|
|
|
# Check permission - only admins can send direct SMS
|
|
if not can_manage_notifications(request.user):
|
|
raise PermissionDenied("You do not have permission to send SMS messages.")
|
|
|
|
if request.method == "POST":
|
|
phone_number = request.POST.get("phone_number", "").strip()
|
|
message = request.POST.get("message", "").strip()
|
|
|
|
# Validate inputs
|
|
errors = []
|
|
if not phone_number:
|
|
errors.append(_("Phone number is required."))
|
|
elif not phone_number.startswith("+"):
|
|
errors.append(_("Phone number must include country code (e.g., +966501234567)."))
|
|
|
|
if not message:
|
|
errors.append(_("Message is required."))
|
|
elif len(message) > 1600:
|
|
errors.append(_("Message is too long. Maximum 1600 characters."))
|
|
|
|
if errors:
|
|
for error in errors:
|
|
messages.error(request, error)
|
|
return render(
|
|
request,
|
|
"notifications/send_sms_direct.html",
|
|
{
|
|
"phone_number": phone_number,
|
|
"message": message,
|
|
},
|
|
)
|
|
|
|
try:
|
|
# Clean phone number
|
|
phone_number = phone_number.replace(" ", "").replace("-", "").replace("(", "").replace(")", "")
|
|
|
|
# Send SMS
|
|
notification_log = NotificationService.send_sms(
|
|
phone=phone_number,
|
|
message=message,
|
|
metadata={
|
|
"sent_by": str(request.user.id),
|
|
"sent_by_name": request.user.get_full_name(),
|
|
"source": "direct_sms_send",
|
|
},
|
|
)
|
|
|
|
# Log the action
|
|
from apps.core.services import AuditService
|
|
|
|
AuditService.log_event(
|
|
event_type="sms_sent_direct",
|
|
description=f"Direct SMS sent to {phone_number} by {request.user.get_full_name()}",
|
|
user=request.user,
|
|
metadata={
|
|
"phone_number": phone_number,
|
|
"message_length": len(message),
|
|
"notification_log_id": str(notification_log.id) if notification_log else None,
|
|
},
|
|
)
|
|
|
|
messages.success(request, _(f"SMS sent successfully to {phone_number}."))
|
|
return redirect("notifications:send_sms_direct")
|
|
|
|
except Exception as e:
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger.error(f"Error sending direct SMS: {str(e)}", exc_info=True)
|
|
messages.error(request, f"Error sending SMS: {str(e)}")
|
|
|
|
return render(request, "notifications/send_sms_direct.html")
|
|
|
|
|
|
# ============================================================================
|
|
# NOTIFICATION INBOX VIEWS
|
|
# ============================================================================
|
|
|
|
|
|
@login_required
|
|
def notification_inbox(request):
|
|
"""
|
|
Notification inbox page.
|
|
Displays all non-dismissed notifications for the current user.
|
|
"""
|
|
from .models import UserNotification
|
|
|
|
filter_type = request.GET.get("filter", "all")
|
|
|
|
# Base queryset - exclude dismissed
|
|
notifications = UserNotification.objects.filter(user=request.user, is_dismissed=False)
|
|
|
|
# Apply filters
|
|
if filter_type == "unread":
|
|
notifications = notifications.filter(is_read=False)
|
|
elif filter_type == "read":
|
|
notifications = notifications.filter(is_read=True)
|
|
|
|
# Pagination
|
|
paginator = Paginator(notifications, 20)
|
|
page = request.GET.get("page", 1)
|
|
notifications_page = paginator.get_page(page)
|
|
|
|
context = {
|
|
"notifications": notifications_page,
|
|
"filter": filter_type,
|
|
"unread_count": UserNotification.objects.filter(user=request.user, is_read=False, is_dismissed=False).count(),
|
|
}
|
|
|
|
return render(request, "notifications/inbox.html", context)
|
|
|
|
|
|
@login_required
|
|
def notification_list_api(request):
|
|
"""
|
|
API endpoint to get notifications list (JSON).
|
|
"""
|
|
from .models import UserNotification
|
|
from django.http import JsonResponse
|
|
|
|
filter_type = request.GET.get("filter", "all")
|
|
page = int(request.GET.get("page", 1))
|
|
|
|
notifications = UserNotification.objects.filter(user=request.user, is_dismissed=False)
|
|
|
|
if filter_type == "unread":
|
|
notifications = notifications.filter(is_read=False)
|
|
elif filter_type == "read":
|
|
notifications = notifications.filter(is_read=True)
|
|
|
|
paginator = Paginator(notifications, 20)
|
|
notifications_page = paginator.get_page(page)
|
|
|
|
data = {
|
|
"notifications": [
|
|
{
|
|
"id": str(n.id),
|
|
"title": n.get_title(),
|
|
"message": n.get_message(),
|
|
"type": n.notification_type,
|
|
"is_read": n.is_read,
|
|
"created_at": n.created_at.isoformat(),
|
|
"action_url": n.action_url,
|
|
}
|
|
for n in notifications_page
|
|
],
|
|
"has_next": notifications_page.has_next(),
|
|
"has_previous": notifications_page.has_previous(),
|
|
"page": page,
|
|
"total_pages": paginator.num_pages,
|
|
}
|
|
|
|
return JsonResponse(data)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def mark_notification_read(request, notification_id):
|
|
"""
|
|
Mark a single notification as read.
|
|
"""
|
|
from .models import UserNotification
|
|
from django.http import JsonResponse
|
|
|
|
try:
|
|
notification = UserNotification.objects.get(id=notification_id, user=request.user)
|
|
notification.mark_as_read()
|
|
return JsonResponse({"success": True})
|
|
except UserNotification.DoesNotExist:
|
|
return JsonResponse({"success": False, "error": "Notification not found"}, status=404)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def mark_all_notifications_read(request):
|
|
"""
|
|
Mark all notifications as read.
|
|
"""
|
|
from .models import UserNotification
|
|
from django.http import JsonResponse
|
|
from django.utils import timezone
|
|
|
|
UserNotification.objects.filter(user=request.user, is_read=False, is_dismissed=False).update(
|
|
is_read=True, read_at=timezone.now()
|
|
)
|
|
|
|
return JsonResponse({"success": True})
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def dismiss_notification(request, notification_id):
|
|
"""
|
|
Dismiss a notification.
|
|
"""
|
|
from .models import UserNotification
|
|
from django.http import JsonResponse
|
|
|
|
try:
|
|
notification = UserNotification.objects.get(id=notification_id, user=request.user)
|
|
notification.mark_as_dismissed()
|
|
return JsonResponse({"success": True})
|
|
except UserNotification.DoesNotExist:
|
|
return JsonResponse({"success": False, "error": "Notification not found"}, status=404)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def dismiss_all_notifications(request):
|
|
"""
|
|
Dismiss all notifications.
|
|
"""
|
|
from .models import UserNotification
|
|
from django.http import JsonResponse
|
|
from django.utils import timezone
|
|
|
|
UserNotification.objects.filter(user=request.user, is_dismissed=False).update(
|
|
is_dismissed=True, dismissed_at=timezone.now()
|
|
)
|
|
|
|
return JsonResponse({"success": True})
|
|
|
|
|
|
@login_required
|
|
def unread_notification_count(request):
|
|
"""
|
|
Get unread notification count.
|
|
"""
|
|
from .models import UserNotification
|
|
from django.http import JsonResponse
|
|
|
|
count = UserNotification.objects.filter(user=request.user, is_read=False, is_dismissed=False).count()
|
|
|
|
return JsonResponse({"count": count})
|
|
|
|
|
|
@login_required
|
|
def latest_notifications(request):
|
|
"""
|
|
Get latest 5 unread notifications for dropdown.
|
|
"""
|
|
from .models import UserNotification
|
|
from django.http import JsonResponse
|
|
|
|
notifications = UserNotification.objects.filter(user=request.user, is_read=False, is_dismissed=False)[:5]
|
|
|
|
data = {
|
|
"notifications": [
|
|
{
|
|
"id": str(n.id),
|
|
"title": n.get_title(),
|
|
"message": n.get_message()[:100] + "..." if len(n.get_message()) > 100 else n.get_message(),
|
|
"type": n.notification_type,
|
|
"created_at": n.created_at.isoformat(),
|
|
"action_url": n.action_url,
|
|
}
|
|
for n in notifications
|
|
],
|
|
"has_more": UserNotification.objects.filter(user=request.user, is_read=False, is_dismissed=False).count() > 5,
|
|
}
|
|
|
|
return JsonResponse(data)
|