Envio segmentado por plataforma

TLDR: Adiciona o campo platform_target em Notification para permitir ao admin enviar uma push diretamente para o tópico platform_ios ou platform_android via Firebase, reutilizando o NotificationService.

Contexto

A spec anterior (20260522145722_notifications_platform_topics.md) criou a infraestrutura de tópicos platform_ios / platform_android (campo topics, service, backfill). Agora o admin precisa poder usar esses tópicos: criar uma Notification no Django Admin e marcar que ela deve ir para todos os iOS ou todos os Android.

Hoje o NotificationService._get_target só conhece duas formas de rotear: send_to_all (broadcast em all_<env>) e push_device (token único). Falta o caso intermediário: broadcast por plataforma.

Objetivos

  • Adicionar o campo platform_target em Notification (choices ios/android, opcional)
  • Atualizar _get_target para priorizar platform_target quando preenchido
  • Quando platform_target está preenchido, a notificação vai para o tópico platform_<choice> independente de send_to_all ou push_device
  • Sem alterações no payload data por padrão (o admin preenche manualmente se quiser)
  • Mostrar platform_target na lista do Django Admin
  • Validar no admin que platform_target e send_to_all são mutuamente exclusivos

Fora de escopo

— (não registrado na spec original)

Mudanças

apps/notifications/models.py

Adicionar em Notification: python PLATFORM_CHOICES = (("ios", "iOS"), ("android", "Android")) platform_target = models.CharField( blank=True, null=True, max_length=20, choices=PLATFORM_CHOICES, verbose_name="Plataforma alvo", help_text="Se preenchido, envia para todos os dispositivos da plataforma escolhida (ignora 'Enviar para todos?' e dispositivo específico).", )

apps/notifications/migrations/0018_notification_platform_target.py (novo)

Schema migration adicionando platform_target como nullable.

apps/notifications/services/notification.py

Atualizar _get_target: python def _get_target(self) -> _NotificationTarget: if self.notification.platform_target: return _NotificationTarget(kind="topic", value=f"platform_{self.notification.platform_target}") if self.notification.send_to_all: return _NotificationTarget(kind="topic", value=self.all_topic()) token = getattr(self.notification.push_device, "token", None) if token: return _NotificationTarget(kind="token", value=token) return _NotificationTarget(kind="none", value=None)

Precedência: platform_target > send_to_all > push_device.

apps/notifications/admin.py

  • list_display do NotificationAdmin passa a incluir platform_target
  • Adicionar NotificationAdminForm com validação em clean() que levanta ValidationError quando platform_target e send_to_all vêm juntos; associar via form = NotificationAdminForm

tests/notifications/test_notification_service.py

Testes Triple-A com messaging.send mockado: - platform_target='ios' → mensagem enviada com topic="platform_ios" - platform_target='android' → topic="platform_android" - platform_target='ios' + send_to_all=True → platform_target vence - platform_target='ios' + push_device definido → platform_target vence - platform_target vazio + send_to_all=True → comportamento existente preservado (all_<env>)

tests/notifications/test_notification_admin.py (novo)

Testes de validação do form do admin, cobrindo as combinações válidas e inválidas de platform_target e send_to_all.

Como verificar

  1. python manage.py migrate notifications — verificar 0018 aplicada
  2. Django Admin → Notifications → criar nova com platform_target='ios' e salvar
  3. Confirmar no log/banco que a mensagem FCM foi enviada com topic="platform_ios" e que apenas devices iOS recebem
  4. Criar outra com platform_target='android' e send_to_all=True ao mesmo tempo — deve exibir erro de validação impedindo o save
  5. Criar outra com send_to_all=True e platform_target vazio — deve continuar indo para all_<env> (regressão)
  6. pytest tests/notifications/ — todos passam

Documentação