Continuando con los artículos sobre django, en este caso se usará django-celery para envío de mensajes a un servidor kannel (servidor sms) y django-tastypie para mostrar el resultado por medio de API rest full con json.
En los dos artículos anteriores Manejo de colas de RabbitMQ en django con django-celery y Restfult API con django tastypie se tiene la base de este artículo. Este artículo se basa en el ejemplo de la mensajería sms del artículo de Restfult API con django tastypie (todo el proceso de creación del proyecto y de la aplicación sms fue explicada en ese artículo).
La idea ahora es poder mostrar los sms listados por evento, no es necesario estar creando una tabla que herede de las otras dos, esto será posible gracias a tastypie en una consulta del json.
Se usará como base de datos sqlite3. A continuación se muestra el archivo settings.py:
#Archivo settings.py:
#Configuración de la base de datos sqlite3
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'sms.db', # Or path to database file if using sqlite3.
'USER': '', # Not used with sqlite3.
'PASSWORD': '', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}
#Zona horaria y localización
TIME_ZONE = 'America/Caracas'
LANGUAGE_CODE = 'es-ve' #Aplicaciones instaladas (djcelery,pasarela.apps.sms,tastypie y south)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
# Uncomment the next line to enable the admin:
'django.contrib.admin',
'djcelery',
'pasarela.apps.sms',
'tastypie',
'south',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
)
#Conexión al servidor kannel
direccion = "127.0.0.1"
portsms = "13013"
portadmin = "13000"
usuario = "kannel"
clave = "kannel"
----------------------
Elarchivo pasarela/apps/sms/models.py:
#Archivo pasarela/apps/sms/models.py
fromdjango.db import models
#Se crea la tabla Evento que contiene los campos evento (número del evento), el estatus del mismo (si se #termino el evento o no y si no termino fue por falla o no.
class Evento(models.Model):
evento = models.IntegerField(primary_key=True)
estatus = models.BooleanField(default=False)
defunicode(self):
evento = "Evento: %s, Estatus: %s , Falla: %s" %(self.evento,self.estatus,self.falla)
returnevento
#Se crea la tabla HistoricoSMS donde se tiene un campo
#foreignkey de la tablla evento, el mensaje, el número de celular, el estatus si se envío o no o si fallo.
class HistoricoSMS(models.Model):
evento = models.ForeignKey(Evento)
mensaje = models.CharField(max_length=150)
numcel = models.CharField(max_length=11)
estatus = models.BooleanField(default=False)
defunicode(self):
mensaje = "%s, %s, %s, %s, %s" %(self.evento.evento,self.numcel,self.estatus, self.mensaje,self.falla)
returnmensaje
--------------------
Archivopasarela/apps/sms/admin.py:
#Archivo pasarela/apps/sms/admin.py
#En este archivo se define que las tablas Evento e HistoricoSMS se puedan visualizar desde la #administración de django.
fromdjango.contrib import admin
frompasarela.apps.sms.models import HistoricoSMS,Evento
admin.site.register(HistoricoSMS)
admin.site.register(Evento)
Archivo pasarela/apps/sms/api.py (archivo para crear el api restful de django-tastypie), en este caso tiene varias modificaciones con respecto al artículo anterior sobre tastypie, ahora se agrega la variable filtering(más información sobre filtering en el siguiente enlace) que es un diccionario en cada recurso:
#Se importa de tastypie.resources ModelResource, ALL y ALL_WITH_RELATIONS
fromtastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
#Se importa del modelo HistoricoSMS y Evento
from .models import HistoricoSMS,Evento
#Se importa fields de tastypie
fromtastypie import fields
class EventoResource(ModelResource):
class Meta:
queryset =Evento.objects.all()
resource_name = 'evento'
#Se muestra todos los eventos
filtering = {
'evento': ALL,
}
classSMSResource(ModelResource):
evento = fields.ForeignKey(EventoResource, 'evento')
class Meta:
queryset = HistoricoSMS.objects.all()
resource_name = 'sms'
#De evento se muestra todo con relación a el. de estatus se muestra exactamente lo que se necesita,
#acá también se puede usar para las consultas: ['exact','range','gt','gte','lt','lte'].
filtering = {
'evento': ALL_WITH_RELATIONS,
'estatus':['exact'],
}
------------------------
El archivo que permite manejar las tareas de celery pasarela/apps/sms/tasks.py :
#Archivo pasarela/apps/sms/tasks.py
from celery import Celery
app = Celery('tasks', broker='amqp://',backend='amqp')
from urllib2 import urlopen
from urllib import urlencode
import urllib2
importjson
from time import sleep
fromdjango.conf import settings
frompasarela.apps.sms.models import HistoricoSMS,Evento
#Conexión al servidor kannel (está configuración se puede pasar al archivo settings.py y usarla desde allí
direccion = settings.direccion
portsms = settings.portsms
portadmin = settings.portadmin
usuario = settings.usuario
clave = settings.clave
#Se defineuna tarea para celery por medio del decorador @task, se recibe un json para luego sea
# procesado.
@app.task
def RecNums(datos):
#Se toma el json y se convierte en un diccionario
diccionario = json.loads(datos)
#Se instancia la clave Evento(tabla Evento).
evento = Evento()
#Se asigna cada variable del diccionario para trabajarlosdirectamente
forclave in diccionario.keys():
ifclave == 'mensaje':
mensaje = diccionario[clave]
elifclave == 'numeros':
numeros = diccionario[clave]
elifclave == 'cantnumeros':
cantnum = int(diccionario[clave])
elifclave == 'evento':
eventoid = int(diccionario[clave])
#Se crea una lista para agregar todos los números de celular a dicha lista
lista = []
fornum in numeros: lista.append(str(num))
#A evento.evento se le asigna el id del evento.
evento.evento = eventoid
#Si la lista es distinta a la variable cantnum se envía un mensaje de error, si no se procesa la lista
iflen(lista) == cantnum:
#Se envía al proceso Enviar (de celery) cada mensaje con su número celular, esperando un segundo
#para procesarel siguiente.
fori in range(len(lista)):
#Se Envía el mensaje pasando el evento, el número celular de la lista y el mensaje
resultado = Enviar.delay(evento,lista[i],mensaje)
sleep(1)
#Se asigna True al estatus del evento al terminar de procesar los mensajes.
evento.estatus = True
#Se salva los valores en la tabla Evento.
evento.save()
return"Se enviaron: %s" %cantnum
else:
evento.estatus = False
evento.save()
return"Error en la recepcion de losdatos"
#Se crea la tarea de celery Enviar donde recibe el número del evento, el número de celular y la cantidad de
#intentos para enviarel sms el valor por defecto es 5.
@app.task
def Enviar(evento,numcel,mensaje,intentos=5):
#Se instancia la clase HistoricoSMS que maneja dicha table de models.py
historico = HistoricoSMS()
#Se le da forma de códificación url al mensaje para eliminar los espacios en blanco del mismo.
form = urlencode({'text': mensaje})
#se define la variable Url donde se tiene el url del servidor kannel donde se le pasa al texto
#el usuario, la clave, el número celular y el mensaje a enviar.
Url = "http://%s:%s/cgi-bin/sendsms?username=%s&password=%s&to=%s&text=%s" % (direccion,portsms,usuario,clave,numcel,form)
#Se maneja una excepción si hay un error de comunicación http.
try:
#Se abre el Url
f = urlopen(Url,timeout=10)
#Se asigna los valores numcel, mensaje y evento al objeto historico.
historico.numcel = numcel
historico.mensaje = mensaje
historico.evento = evento
#Se lee el resultado de abrir el url, si se tiene el mensaje de encolado para enviar más tarde
#se asigna el estatus False y se salva devolviendo el mensaje de no enviado
iff.read() '3: Queued for later delivery':
historico.estatus = False
historico.save()
return 'Mensaje no enviado a: %s' %numcel
else:
#Se envío el mensaje se coloca el estatus en True y se salva en la tabla
historico.estatus = True
historico.save()
#Se devuelve el mensaje de mensaje enviado.
return 'Mensaje enviado a: %s' %numcel
except (urllib2.URLError,urllib2.HTTPError),exc:
#Si hay una excepción de error http se reintenta el envío llamando de forma
#concurrente a esta misma función reduciendo el valor de los intentos,
#cuando llegue a cero el número de intentos se devuelve un mensaje de no enviado
ifintentos 0:
Enviar(evento,numcel,mensaje,intentos-1)
else:
#Se salva los valores en la tabla y devuelve el mensaje de sms no enviado
historico.numcel = numcel
historico.mensaje = mensaje
historico.evento = evento
historico.estatus = False
historico.save()
return'No hay conexion al kannel por puerto o IP, el numero que no se procesaron es: %s' %numcel
----------------------------
A continuación se muestra el contenido del archivo pasarela/urls.py el cual contiene el acceso al API como se explico en el artículo anterior y ahora tiene el acceso a la función que permite recibir los datos para enviar (estos datos se pasan por un json):
#Archivo pasarela/urls.py
fromdjango.conf.urls import patterns, include, url
fromdjcelery import views as celery_views
frompasarela.apps.sms import tasks
# Uncomment the next two lines to enable the admin:
fromdjango.contrib import admin
admin.autodiscover()
#Se importa Api de tastypie.api
fromtastypie.api import Api
from apps.sms.api import SMSResource,EventoResource
v1_api = Api(api_name='v1')
#Se registra los recursos en la instancia del api.
v1_api.register(SMSResource())
v1_api.register(EventoResource())
urlpatterns = patterns('',
# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
#Acceso al API
url(r'^api/', include(v1_api.urls)),
#se define enviar que usa la vista de tareas de celery para ejecutar tasks.RecNums
url(r'^enviar/', celery_views.task_view(tasks.RecNums)),
#Consulta a celery si la tarea se ejecuto sin problemas
url(r'^(?P[\w\d-]+)/done/?$', celery_views.is_task_successful,
name="celery-is_task_successful"),
#Consulta de celery para ver el estatus de la tarea
url(r'^(?P[\w\d-]+)/status/?$', celery_views.task_status,
name="celery-task_status"),
)
-----------------------
La base de datos se sincroniza como se explico en los artículos anteriores, ahora toca ejecutar en modo pruba django-celery, django como tal y abrir un interprete de comandos para django.
Ejecución de django-celery con dos colas de rabbitMQ y en modo info:
python manage.pyceleryd -E -l info -c 2
A continuación captura de pantalla de la ejecución:
Note que se tienen 2 tareas en el celery:
- pasarela.apps.sms.tasks.Enviar
- pasarela.apps.sms.tasks.RecNums
Ahora se ejecuta django en el 127.0.0.1 y puerto 8080:
pythonmanage.pyrunserver 127.0.0.1:8080
A continuación una captura de pantalla de la ejecución del comando:
Y por último se ejecuta el comando para tener un shell interfactivo de django:
pythonmanage.py shell
A continuación la captura de pantalla:
Ahora se ejecutará en el shell una prueba que simula la recepción por parte de la tarea del json que recibe de una aplicación externa para enviar los sms:
Hay que acotar que los números de celular de la prueba no corresponden a números reales o de proveedores de teléfonía celular.
Ahora para complementar la idea de consulta API restful por json del artículo anterior la idea es listar los números celulares que se enviaron del evento 6, en este caso se usará el programa curl:
curl http://127.0.0.1:8080/api/v1/sms/?evento=6
El resultado es:
{"meta": {"limit": 20, "next": null, "offset": 0, "previous": null, "total_count": 5}, "objects": [{"estatus":true, "evento": "/api/v1/evento/6/", "id": "21", "mensaje": "Esta es una prueba de sms", "numcel": "14225673531", "resource_uri": "/api/v1/sms/21/"}, {"estatus":true, "evento": "/api/v1/evento/6/","id": "22", "mensaje": "Esta es una prueba de sms", "numcel": "142165673531", "resource_uri": "/api/v1/sms/22/"}, {"estatus": true, "evento": "/api/v1/evento/6/","id": "23", "mensaje": "Esta es una prueba de sms", "numcel": "14265673531", "resource_uri": "/api/v1/sms/23/"}, {"estatus": true, "evento": "/api/v1/evento/6/", "id": "24", "mensaje": "Esta es una prueba de sms", "numcel": "14145673531", "resource_uri": "/api/v1/sms/24/"}, {"estatus": true, "evento": "/api/v1/evento/6/","id": "25", "mensaje": "Esta es una prueba de sms", "numcel": "14245673531", "resource_uri": "/api/v1/sms/25/"}]}
Por último se muestra la captura de pantalla de lo que se genera en el djcelery:
En el próximo artículo se mostrará el acceso a las tareas de celery desde el URL definido en urls.py.