You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

119 lines
4.6 KiB

import asyncio
import logging
import string
from apps.account.models import Notification, NotificationTemplate
logger = logging.getLogger(__name__)
class SafeFormatter(string.Formatter):
def get_value(self, key, args, kwargs):
if isinstance(key, str):
if key not in kwargs:
return f"{{{key}}}"
return kwargs[key]
return super().get_value(key, args, kwargs)
def get_template_context(user, notification_type, data):
"""
Builds a dynamic context dictionary containing user info to format templates.
"""
context = {
'student_name': getattr(user, 'fullname', user.username) or user.username,
'fullname': getattr(user, 'fullname', user.username) or user.username,
'username': user.username,
}
return context
def resolve_notification_route(notification_type, data):
"""
Resolves standard GoRouter paths based on notification type and payload data.
"""
return None
def create_and_send_notification(user, title_en, body_en, title_fa=None, body_fa=None, service='imam-javad',
data=None, notification_type=None, action='navigate', navigate_to=None,
title_ru=None, body_ru=None):
"""
Creates a Notification record in the database and sends a push notification to FCM.
Loads and renders the NotificationTemplate from the database if present.
If the template is inactive, the notification is discarded.
"""
# Map Persian arguments to Russian to preserve backward compatibility with existing signals/tasks
title_ru = title_ru or title_fa
body_ru = body_ru or body_fa
# Auto-resolve notification type from data payload if not provided
if not notification_type and isinstance(data, dict):
notification_type = data.get('type')
# 1. Check for template settings in the database
if notification_type:
template = NotificationTemplate.objects.filter(notification_type=notification_type).first()
if template:
if not template.is_active:
logger.info(f"Notification type '{notification_type}' is disabled via database template settings.")
return None
# Override templates with database values
title_ru = template.title_ru
title_en = template.title_en
body_ru = template.body_ru
body_en = template.body_en
# 2. Render templates safely with dynamic context
context = get_template_context(user, notification_type, data)
formatter = SafeFormatter()
try:
title_ru = formatter.format(title_ru, **context) if title_ru else ""
title_en = formatter.format(title_en, **context) if title_en else ""
body_ru = formatter.format(body_ru, **context) if body_ru else ""
body_en = formatter.format(body_en, **context) if body_en else ""
except Exception as e:
logger.error(f"Error formatting templates: {e}")
# Determine user localized text
lang = getattr(user, 'language', None)
lang_code = lang.code if lang and hasattr(lang, 'code') else 'ru'
title = title_ru if lang_code == 'ru' else title_en
message = body_ru if lang_code == 'ru' else body_en
# Auto-resolve GoRouter navigation route if not provided
if not navigate_to and notification_type:
navigate_to = resolve_notification_route(notification_type, data)
# Save to database
from apps.account.tasks import send_notification
notif = Notification.objects.create(
user=user,
title=title,
message=message,
service=service,
notification_type=notification_type,
action=action,
navigate_to=navigate_to
)
fcm_token = getattr(user, 'fcm', None)
if fcm_token:
# Prepare the payload including the new routing properties
fcm_data = dict(data or {})
fcm_data.setdefault('type', notification_type or '')
fcm_data.setdefault('action', action or '')
fcm_data.setdefault('navigate_to', navigate_to or '')
try:
try:
loop = asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
if loop.is_running():
loop.create_task(send_notification([fcm_token], title, message, fcm_data))
else:
loop.run_until_complete(send_notification([fcm_token], title, message, fcm_data))
except Exception as e:
logger.error(f"Failed to send push notification via FCM: {e}")
return notif