Sistemas de notificaciones

Hoy vamos a aprender como enviar notificaciones push a dispositivos iOS y Android desde nuestra aplicación Django.

Cuando disponemos de una aplicación de smartphone que tiene comunicación con servidor, muchas veces nos interesará poder enviarle mensajes desde el servidor (como ofertas, recordatorios o novedades), que pueda recibir el usuario a pesar de que la aplicación no esté funcionando en ese momento en su dispositivo.

Para eso, Apple implementó un sistema de notificaciones para iOS(push notifications), que luego replicó de forma similar Google en Android. Vamos a ver un diagrama de como funciona el sistema de Apple (A pple P ush N otifications S erver):
diagrama notificaciones push apple

Como vemos, la aplicación se registra vía iOS para habilitar notificaciones push, pide un identificador único (token) al servidor APNS de apple, y la propia app se lo tiene que enviar a nuestro servidor para que podamos comunicarnos con ella en ese dispositivo concreto. Posteriormente, cuando queremos enviarle una notificación a un dispositivo, lo hacemos enviándole el mensaje y el token de identificación del mismo al servidor APNS de Apple, y ellos se encargan del resto.

Podemos intuir que el sistema de google (G oogle C loud M essaging) será similar. En todo caso, lo importante es que tanto APNS como GCM nos proporcionan una infraestructura que nos permite enviar mensajes desde nuestro servidor hacia un dispositivo concreto.

Cómo no, la comunidad de Django sale al rescate, y nos simplifica la vida con el paquete django-push-notifications.

Instalando django-push-notifications

Para el ejemplo, partiremos de un nuevo proyecto que gestionaremos en un entorno virtual mediante virtualenvwrapper. No es necesario para seguir el tutorial, pero es una buena práctica. (Si necesitas ayuda para crear el proyecto, mira el tutorial crear un proyecto en Django).

Descarga

Activamos nuestro entorno virtual y descargamos el paquete:

$ workon miEntorno
(miEntorno)$ pip install django-push-notifications

Incluimos también sus dependencias:

(miEntorno)$ pip install django-uuidfield

Finalmente, aprovechamos para actualizar a nuestro fichero de dependencias de pip

(miEntorno)$ pip freeze > requirements.txt

 

Puesta en marcha

Añadimos la app a nuestro módulo settings (mi_proyecto/mi_proyecto/settings.py), y configuramos los certificados de publicación:

INSTALLED_APPS = (
...
'push_notifications'
)

PUSH_NOTIFICATIONS_SETTINGS = {
'GCM_API_KEY': '< your api key >',
'APNS_CERTIFICATE': '/path/to/your/certificate.pem',
}

Exísten otros parámetros de configuración, pero los importantes son éstos, que dependerán de nuestros certificados, es decir, lo que valida que tenemos derechos de publicación de notificaciones sobre una aplicación determinada.

 

Obteniendo los certificados de publicación

Los certificados de publicación de notificaciones push van vinculados a la aplicación desarrollada, por lo que se suelen generar en el proceso de crear la aplicación en el marketplace de turno. Existe mucha literatura acerca de como preparar una aplicación iOS/Android para poder enviarle push notifications y obtener los certificados necesarios, por lo que no voy a perder tiempo con eso. Simplemente, te dirijo a buenos tutoriales:

Peculiaridades iOS, conviertiendo el certificado a .pem

Un punto a destacar, cuando hablamos de iOS, es que normalmente exportamos el certificado en formato .p12 (si has seguido los pasos del tutorial anterior, deberías haberlo hecho).

El tipo de certificado que utilizamos desde Django está en formato .pem, por lo que habrá que hacer un trabajo extra de conversión:

Abrimos la consola, vamos a donde hemos exportado el certificado (en este caso lo hemos llamado certificado.p12), e introducimos lo siguiente para pasar del formato .p12 al formato .pem

$ openssl pkcs12 -in certificado.p12 -out apns-dev.pem -nodes -clcerts

Este certificado apns-dev.pem es el que deberemos meter en nuestro proyecto Django, y indicar en el módulo settings, mediante la variable APNS_CERTIFICATE.

 

Primeras pruebas

Django push notifications viene con vistas para gestionar los dispositivos desde el panel de administración, por lo que, antes de entrar a tocar código, veamos si está funcionando:

Sincronizamos BBDD

Dado que hemos instalado una nueva aplicación en nuestro proyecto Django, vamos a actualizar las tablas:

(miEntorno)$ ./manage.py syncdb

Ejecutamos servidor

(miEntorno)$ ./manage.py runserver

Accedemos al panel de administración

Suponiendo que nuestro proyecto Django tiene acceso al panel de administración, es decir:

  • en mi_proyecto/mi_proyecto/settings.py tenemos django.contrib.admin como installed app:
INSTALLED_APPS = (
'django.contrib.admin',
...
  • en mi_proyecto/mi_proyecto/urls.py hemos incluido la vista de administración:
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
...

Podremos acceder al panel de administración, donde veremos un grupo denominado Push_Notifications en el que podremos incluso incorporar dispositivos tanto iOS (APNS) como Android (GCM).

Panel de administración de Django

No solo eso, si no que tras introducir algunos dispositivos, si los seleccionamos, podremos enviarles una notificación de test desde el menú acción correspondiente, de la vista APNS devices o la vista GCM devices.

Enviando notificaciones push

django-push-notifications implementa dos modelos: GCMDevice (para Android) y APNSDevice (para iOS), cuyos campos son indénticos:

  • name (opcional): Nombre del dispositivo
  • is_active (default True): Booleano que indica si al dispositivo se le deben enviar notificaciones
  • user (opcional): Una foreign key a auth.User, por si quieres vincular el dispositivo a un usuario concreto de nuestra BBDD
  • device_id (opcional): El UUID del dispositivo, obtenido mediante las APIs de Android o iOS, por si queremos identificarlo de forma única.
  • registration_id (requerido): El identificador GCM o token APNS del dispositivo.

Visto lo básico, ahora vamos a hacer las cosas un poco más en serio, y crearemos una vista con algunos botones que nos permitan enviar esas notificaciones, y veremos como se produce el envío en el código.

 

Creamos una aplicación para nuestras notificaciones

(miEntorno)$: ./manage.py startapp pushes

Nuestro proyecto tendrá ahora una estructura como la siguiente:

mi_proyecto
/db.sqlite3
/manage.py
/pushes
/__init__.py
/admin.py
/models.py
/tests.py
/views.py
/mi_proyecto
/

 

Añadiendo templates y algo de estilo

Vamos a crear también una carpeta static que cuelgue de mi_proyecto, donde guardaremos los templates que vamos a usar y el CSS y JS de Bootstrap (ya lo explicaré en otro post, hoy lo usaremos a pelo), para que nuestra vista tenga un aspecto más agradable. Nuestra estructura quedará así (no añadir aún las carpetas css, js y fonts):

mi_proyecto
/db.sqlite3
/manage.py
/pushes
/
/mi_proyecto
/
/static
/static
/css
/js
/fonts
/templates

Incorporamos Bootstrap

  1. Nos dirigimos a http://getbootstrap.com/
  2. Descargamos el paquete de bootstrap como vemos en la imagenDescarga de Bootstrap
  3. Abrimos el paquete y vemos que contiene las carpetas css, js y fonts
  4. Añadimos dichas carpetas a nuestro directorio mi_proyecto/static/static para que quede como hemos visto en la estructura anterior.

Creamos el template básico

En la carpeta templates creamos un archivo denominado base.html donde meteremos el siguiente código:





Demo project <!-- Bootstrap core CSS --> <!-- Custom styles for this template --> <!-- Custom CSS -->   <div class="navbar navbar-inverse navbar-fixed-top"> <div class="container"> <div class="navbar-header"><a class="navbar-brand" href="#">Demo project</a></div> </div> </div> {% block jumbotron %} {% endblock %} <div class="container"> <div class="row-fluid">{% block content %} {% endblock %}</div> <hr /> <footer>© -KaiK-reations 2014 </footer></div> <!-- /container --> <!-- Bootstrap core JavaScript ================================================== --> <!-- Placed at the end of the document so the pages load faster --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <script src="/static/js/bootstrap.min.js"></script>

Es una estructura muy básica, que nos servirá para utilizar el ejemplo jumbotron de bootstrap.

Hay que destacar 2 elementos: {% block jumbotron %} y {% block content %}, que es donde meteremos contenido.

Creamos un nuevo fichero en templates que se llamará main.html.
Metemos el siguiente código:

{% extends 'base.html' %}

{% block jumbotron %}
<!-- Jumbotron -->
<div class="jumbotron">
<h1>Send some notifications</h1>
<p class="lead">This is just a simple view to help you send some push notifications.</p>

</div>
{% endblock %}

{% block content %}
{% if message %}
<div class="alert alert-info">{{ message }}</div>
{% endif %}

<form method="POST">{% csrf_token %}
<button class="btn btn-primary" name="code" type="submit" value="android">Push to Androids!</button>
<button class="btn btn-primary" name="code" type="submit" value="ios">Push to iOSs!</button>
<button class="btn btn-primary" name="code" type="submit" value="simple">Push to one device!</button></form>{% endblock %}

Como se puede apreciar, en este archivo incluimos el que hemos creado anteriormente (base.html), definimos el bloque jumbotron donde añadiremos cierto texto predomintante, y definimos el bloque importante, el de contenido:

  • Proporcionamos una alerta de información con la variable message que pasaremos desde el servidor
  • Creamos un formulario con 3 botones, uno por acción que queremos realizar. Identificaremos las acciones en el servidor mediante la variable code que usamos en cada botón.
  • Recordar incorporar la variable csrf_token al formulario para que Django nos lo acepte.

Creamos la vista que usará el template

Abrimos el archivo pushes/views.py, e incorporamos el siguiente contenido:

from django.shortcuts import render_to_response, RequestContext
from push_notifications.models import APNSDevice, GCMDevice

def push_notifications_view(request):

if request.method == "POST":
if 'code' in request.POST:
code = request.POST['code']

if code == 'android':
print 'code == android'
devices = GCMDevice.objects.all()
devices.send_message({"message": "Hi Android!"})
message = "message sent to android devices"

elif code == 'ios':
print 'code == ios'
devices = APNSDevice.objects.all()
devices.send_message("Hi iOS!")
message = "message sent to ios devices"

elif code == 'simple':
print 'code == simple'
device = APNSDevice.objects.get(registration_id='mi apns token')
device.send_message(None, extra={"foo": "bar"})
message = "simple message sent"

return render_to_response('main.html', locals(), context_instance=RequestContext(request))

Vamos a explicar un poco el código:

  • Creamos una función para controlar la vista denominada push_notifications_view
  • En caso de no tratarse de una petición POST, renderizamos el template que hemos creado antes (main.html), tal cual
  • En caso de ser un POST (venimos del formulario de main.html) buscamos el valor de la variable code, para actuar según su resultado:
    • si es ‘android’, listamos todos los dispositivos tipo Android que tenemos almacenados y les enviamos el mensaje “Hi Android!”
    • si es ‘ios’, listamos todos los dispositivos tipo iOS que tenemos almacenados y les enviamos el mensaje “Hi iOS!” (Si solo pasamos un string, se envía en el diccionario como “message”)
    • si es ‘simple’, seleccionamos el dipositivo iOS con el identificador APNS ‘mi apns token’ (obviamente este no funciona) y le enviamos un mensaje vacío, con un diccionario añadido {“foo”: “bar”}.
    • En cualquiera de los tres casos anteriores, se renderiza de nuevo el template, aunque primero habremos modificado la variable message.

Editamos las URLs para acceder a la vista

Nuestro archivo pushes/urls.py debería quedar así:

from django.conf.urls import patterns, include, url

urlpatterns = patterns('pushes.views',
url(r'^', 'push_notifications_view', name='push_notifications_view'),
)

Mientras que las urls generales mi_proyecto/urls.py deberían quedar algo así como:

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^pushes/', include('pushes.urls')),
)

Detectando los recursos estáticos en settings

Para encontrar el directorio static con css, js y demás, así como las templates, debemos indicar su localización en el módulo de settings (mi_proyecto/settings.py), por lo que, si habéis seguido la estructura de carpetas que he explicado anteriormente, añadiremos al final:

STATIC_URL = '/static/'

STATIC_ROOT = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'static', 'static-only')

STATICFILES_DIRS = (
os.path.join(os.path.dirname(os.path.dirname(__file__)), 'static', 'static'),
)

TEMPLATE_DIRS = (
os.path.join(os.path.dirname(os.path.dirname(__file__)), 'static', 'templates'),
)

Listo!

Resultados

Si ejecutamos el servidor

(miEntorno)$ ./manage.py runserver

y nos dirigimos a la página que hemos definido en las urls (http://127.0.0.1/pushes), deberíamos ver algo como lo siguiente:

django send notifications

Si hemos hecho las cosas bien, cuando pulsemos uno de los botones, deberíamos ver el alert correspondiente conforme se ha procedido al envío, como vemos en la última imagen:

bootstrap alert class message sent

Y eso ha sido todo por el momento, ¡espero que haya sido de ayuda!