Как обрабатывать уведомления Firebase Cloud Messaging в Django?

Мне удается отправлять FCM-уведомления в мое приложение через Django. Вот моя функция для отправки уведомлений:

import firebase_admin
from firebase_admin import credentials, messaging

cred = credentials.Certificate("service-key.json")
firebase_admin.initialize_app(cred)

def send_push(title, msg, registration_token, dataObject=None):
    try:
        message = messaging.MulticastMessage(
            notification=messaging.Notification(
                title=title,
                body=msg,
            ),
            data=dataObject,
            tokens=registration_token,
        )

        response = messaging.send_multicast(message)
    except messaging.QuotaExceededError:
        raise f'Exceeding FCM notifications quota'

Теперь я буду посылать уведомление внутри представления:

class AdminChangeRole(viewsets.Viewset):
    serializer_class = SomeSerializer
    permission_classes = [IsAdminOnly]
    def post(self, request, *args, **kwargs):
        # code that will change the role of a user
        send_push("Role changed", f"You role was set to {self.role}", fcm_registration_token)
        # return Response

Теперь, после размещения некоторых данных внутри сериализатора и сохранения их. Я надеюсь отправить уведомление для пользователя. Однако я хочу знать, правильно ли я это делаю, включая 2500 параллельных соединений и 400 соединений в минуту.

Предположим, что у вас есть следующий класс FCM для отправки уведомлений:

import threading
from typing import Optional
from firebase_admin import messaging, credentials
import firebase_admin
from coreapp.utils.logging_helper import get_log

log = get_log()

cred = credentials.Certificate(
    "myproject-firebase-adminsdk.json"
)
firebase_admin.initialize_app(cred)


class FCMThread(threading.Thread):
    """
    :param title: Title of notification
    :param msg: Message or body of notification
    :param tokens: Tokens of the users who will receive this notification
    :param data: A dictionary of data fields (optional). All keys and values in the dictionary must be strings.
    :return -> None:
    """

    def __init__(
        self: threading.Thread,
        title: str,
        msg: str,
        tokens: list,
        data: Optional[list] = None,
    ) -> None:
        self.title = title
        self.msg = msg
        self.tokens = tokens
        self.data = data
        threading.Thread.__init__(self)

    def _push_notification(self):
        """
        Push notification messages by chunks of 500.
        """
        chunks = [self.tokens[i : i + 500] for i in range(0, len(self.tokens), 500)]
        for chunk in chunks:
            messages = [
                messaging.Message(
                    notification=messaging.Notification(self.title, self.msg),
                    token=token,
                    data=self.data,
                )
                for token in chunk
            ]
            response = messaging.send_all(messages)
            log.info(f"Number of successful notifications: {response._success_count}")
            log.info(
                f"Number of failed notifications: {len(messages) - response._success_count}"
            )

    def run(self):
        self._push_notification()

Следующий класс может выполняться в другом потоке, если мы используем FCMThread(*args).run(). Исходя из логики моего приложения, я буду отправлять уведомления, которые будут происходить при обновлении и создании базы данных, и я сделал это, переопределив метод save(). Например, следующий код будет отправлять уведомление каждый раз, когда роль пользователя обновляется с классом FCMThread.

class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_("email address"), unique=True)
    name = models.CharField(_("name"), max_length=30, blank=True)
    phone_number = models.CharField(_("phone number"), max_length=20, blank=True)
    date_joined = models.DateTimeField(_("date joined"), auto_now_add=True)
    is_active = models.BooleanField(_("active"), default=True)
    is_staff = models.BooleanField(
        _("staff status"),
        default=False,
        help_text=_("Designates whether the user can log into this admin site."),
    )
    personal_bio = models.CharField(_("persion bio"), max_length=500, blank=True)
    is_verified = models.BooleanField(_("is verified"), default=False)
    is_approved = models.BooleanField(_("is approved"), default=False)
    avatar = models.ImageField(upload_to=user_avatar_path, null=True, blank=True)
    national_id_front = models.ImageField(
        null=True, blank=True, upload_to="users/documents/"
    )
    national_id_back = models.ImageField(
        null=True, blank=True, upload_to="users/documents/"
    )
    roles = models.IntegerField(
        choices=Roles.choices,
        default=Roles.NOT_ASSIGNED,
    )
    community = models.ForeignKey(
        Community,
        related_name="members",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    fcm_token = models.CharField(_("FCM Token"), max_length=200, null=True, blank=True)
    objects = UserManager()

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    class Meta:
        verbose_name = _("user")
        verbose_name_plural = _("users")

    def save(self, *args, **kwargs):
        # send roles notification
        previous_role = None
        if User.objects.filter(id=self.id).exists():
            previous_role = User.objects.get(id=self.id).roles
            if self.roles != previous_role:
                log.info(f"Sending notifcation to {self.name}")
                fcm_tokens = (self.fcm_token,)
                FCMThread(
                    "Role update",
                    f"Your role has been changed from {previous_role} to {self.roles}",
                    fcm_tokens,
                ).start()
         super(User, self).save(*args, **kwargs)

Вернуться на верх